text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "ui/views/controls/scroll_view.h"
  
  #include "base/macros.h"
  #include "base/run_loop.h"
  #include "base/test/icu_test_util.h"
  #include "base/test/scoped_feature_list.h"
  #include "base/test/test_timeouts.h"
  #include "base/threading/thread_task_runner_handle.h"
  #include "build/build_config.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "ui/base/ui_base_features.h"
  #include "ui/compositor/scoped_animation_duration_scale_mode.h"
  #include "ui/events/test/event_generator.h"
  #include "ui/views/border.h"
  #include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
  #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
  #include "ui/views/controls/scrollbar/scroll_bar_views.h"
  #include "ui/views/controls/separator.h"
  #include "ui/views/test/test_views.h"
  #include "ui/views/test/views_test_base.h"
  #include "ui/views/test/widget_test.h"
  
  #if defined(OS_MACOSX)
  #include "ui/base/test/scoped_preferred_scroller_style_mac.h"
  #endif
  
  enum ScrollBarOrientation { HORIZONTAL, VERTICAL };
  
  namespace views {
  namespace test {
  
  class ScrollViewTestApi {
   public:
    explicit ScrollViewTestApi(ScrollView* scroll_view)
        : scroll_view_(scroll_view) {}
  
    BaseScrollBar* GetBaseScrollBar(ScrollBarOrientation orientation) {
      ScrollBar* scroll_bar = orientation == VERTICAL ? scroll_view_->vert_sb_
                                                      : scroll_view_->horiz_sb_;
      return static_cast<BaseScrollBar*>(scroll_bar);
    }
  
    const base::Timer& GetScrollBarTimer(ScrollBarOrientation orientation) {
      return GetBaseScrollBar(orientation)->repeater_.timer_for_testing();
    }
  
    BaseScrollBarThumb* GetScrollBarThumb(ScrollBarOrientation orientation) {
      return GetBaseScrollBar(orientation)->thumb_;
    }
  
    gfx::Point IntegralViewOffset() {
      return gfx::Point() - gfx::ScrollOffsetToFlooredVector2d(CurrentOffset());
    }
  
    gfx::ScrollOffset CurrentOffset() { return scroll_view_->CurrentOffset(); }
  
    base::Timer* GetScrollBarHideTimer(ScrollBarOrientation orientation) {
      return BaseScrollBar::GetHideTimerForTest(GetBaseScrollBar(orientation));
    }
  
    View* corner_view() { return scroll_view_->corner_view_; }
    View* contents_viewport() { return scroll_view_->contents_viewport_; }
  
    Separator* more_content_left() {
      return scroll_view_->more_content_left_.get();
    }
    Separator* more_content_top() {
      return scroll_view_->more_content_top_.get();
    }
    Separator* more_content_right() {
      return scroll_view_->more_content_right_.get();
    }
    Separator* more_content_bottom() {
      return scroll_view_->more_content_bottom_.get();
    }
  
   private:
    ScrollView* scroll_view_;
  
    DISALLOW_COPY_AND_ASSIGN(ScrollViewTestApi);
  };
  
  }  // namespace test
  
  namespace {
  
  const int kWidth = 100;
  const int kMinHeight = 50;
  const int kMaxHeight = 100;
  
  class FixedView : public View {
   public:
    FixedView() {}
    ~FixedView() override {}
  
    void Layout() override {
      gfx::Size pref = GetPreferredSize();
      SetBounds(x(), y(), pref.width(), pref.height());
    }
  
    void SetFocus() { Focus(); }
  
   private:
    DISALLOW_COPY_AND_ASSIGN(FixedView);
  };
  
  class CustomView : public View {
   public:
    CustomView() {}
    ~CustomView() override {}
  
    const gfx::Point last_location() const { return last_location_; }
  
    void Layout() override {
      gfx::Size pref = GetPreferredSize();
      int width = pref.width();
      int height = pref.height();
      if (parent()) {
        width = std::max(parent()->width(), width);
        height = std::max(parent()->height(), height);
      }
      SetBounds(x(), y(), width, height);
    }
  
    bool OnMousePressed(const ui::MouseEvent& event) override {
      last_location_ = event.location();
      return true;
    }
  
   private:
    gfx::Point last_location_;
  
    DISALLOW_COPY_AND_ASSIGN(CustomView);
  };
  
  void CheckScrollbarVisibility(const ScrollView* scroll_view,
                                ScrollBarOrientation orientation,
                                bool should_be_visible) {
    const ScrollBar* scrollbar = orientation == HORIZONTAL
                                     ? scroll_view->horizontal_scroll_bar()
                                     : scroll_view->vertical_scroll_bar();
    if (should_be_visible) {
      ASSERT_TRUE(scrollbar);
      EXPECT_TRUE(scrollbar->visible());
    } else {
      EXPECT_TRUE(!scrollbar || !scrollbar->visible());
    }
  }
  
  ui::MouseEvent TestLeftMouseAt(const gfx::Point& location, ui::EventType type) {
    return ui::MouseEvent(type, location, location, base::TimeTicks(),
                          ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
  }
  
  // This view has a large width, but the height always matches the parent's
  // height. This is similar to a TableView that has many columns showing, but
  // very few rows.
  class VerticalResizingView : public View {
   public:
    VerticalResizingView() = default;
    ~VerticalResizingView() override = default;
    void Layout() override {
      int width = 10000;
      int height = parent()->height();
      SetBounds(x(), y(), width, height);
    }
  
   private:
    DISALLOW_COPY_AND_ASSIGN(VerticalResizingView);
  };
  
  }  // namespace
  
  using test::ScrollViewTestApi;
  
  // Simple test harness for testing a ScrollView directly.
  class ScrollViewTest : public ViewsTestBase {
   public:
    ScrollViewTest() {}
  
    void SetUp() override {
      ViewsTestBase::SetUp();
      scroll_view_ = std::make_unique<ScrollView>();
    }
  
    View* InstallContents() {
      const gfx::Rect default_outer_bounds(0, 0, 100, 100);
      View* contents = new View;
      scroll_view_->SetContents(contents);
      scroll_view_->SetBoundsRect(default_outer_bounds);
      return contents;
    }
  
   protected:
  #if defined(OS_MACOSX)
    void SetOverlayScrollersEnabled(bool enabled) {
      // Ensure the old scroller override is destroyed before creating a new one.
      // Otherwise, the swizzlers are interleaved and restore incorrect methods.
      scroller_style_.reset();
      scroller_style_ =
          std::make_unique<ui::test::ScopedPreferredScrollerStyle>(enabled);
    }
  
   private:
    // Disable overlay scrollers by default. This needs to be set before
    // |scroll_view_| is initialized, otherwise scrollers will try to animate to
    // change modes, which requires a MessageLoop to exist. Tests should only
    // modify this via SetOverlayScrollersEnabled().
    std::unique_ptr<ui::test::ScopedPreferredScrollerStyle> scroller_style_ =
        std::make_unique<ui::test::ScopedPreferredScrollerStyle>(false);
  
   protected:
  #endif
    int VerticalScrollBarWidth() {
      return scroll_view_->vertical_scroll_bar()->GetThickness();
    }
  
    int HorizontalScrollBarHeight() {
      return scroll_view_->horizontal_scroll_bar()->GetThickness();
    }
  
    std::unique_ptr<ScrollView> scroll_view_;
  
   private:
    DISALLOW_COPY_AND_ASSIGN(ScrollViewTest);
  };
  
  // Test harness that includes a Widget to help test ui::Event handling.
  class WidgetScrollViewTest : public test::WidgetTest,
                               public ui::CompositorObserver {
   public:
    static constexpr int kDefaultHeight = 100;
    static constexpr int kDefaultWidth = 100;
  
    WidgetScrollViewTest() {}
  
    // Call this before adding the ScrollView to test with overlay scrollbars.
    void SetUseOverlayScrollers() {
      use_overlay_scrollers_ = true;
    }
  
    // Adds a ScrollView with the given |contents_view| and does layout.
    ScrollView* AddScrollViewWithContents(View* contents,
                                          bool commit_layers = true) {
  #if defined(OS_MACOSX)
      scroller_style_.reset(
          new ui::test::ScopedPreferredScrollerStyle(use_overlay_scrollers_));
  #endif
  
      const gfx::Rect default_bounds(50, 50, kDefaultWidth, kDefaultHeight);
      widget_ = CreateTopLevelFramelessPlatformWidget();
  
      ScrollView* scroll_view = new ScrollView();
      scroll_view->SetContents(contents);
  
      widget_->SetBounds(default_bounds);
      widget_->Show();
  
      widget_->SetContentsView(scroll_view);
      scroll_view->Layout();
  
      widget_->GetCompositor()->AddObserver(this);
  
      // Ensure the Compositor has committed layer changes before attempting to
      // use them for impl-side scrolling. Note that simply RunUntilIdle() works
      // when tests are run in isolation, but compositor scheduling can interact
      // between test runs in the general case.
      if (commit_layers)
        WaitForCommit();
      return scroll_view;
    }
  
    // Adds a ScrollView with a contents view of the given |size| and does layout.
    ScrollView* AddScrollViewWithContentSize(const gfx::Size& contents_size,
                                             bool commit_layers = true) {
      View* contents = new View;
      contents->SetSize(contents_size);
      return AddScrollViewWithContents(contents, commit_layers);
    }
  
    // Wait for a commit to be observed on the compositor.
    void WaitForCommit() {
      base::RunLoop run_loop;
      quit_closure_ = run_loop.QuitClosure();
      base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
          FROM_HERE, quit_closure_, TestTimeouts::action_timeout());
      run_loop.Run();
      EXPECT_TRUE(quit_closure_.is_null()) << "Timed out waiting for a commit.";
    }
  
    void TestClickAt(const gfx::Point& location) {
      ui::MouseEvent press(TestLeftMouseAt(location, ui::ET_MOUSE_PRESSED));
      ui::MouseEvent release(TestLeftMouseAt(location, ui::ET_MOUSE_RELEASED));
      widget_->OnMouseEvent(&press);
      widget_->OnMouseEvent(&release);
    }
  
    // testing::Test:
    void TearDown() override {
      widget_->GetCompositor()->RemoveObserver(this);
      if (widget_)
        widget_->CloseNow();
      WidgetTest::TearDown();
    }
  
   protected:
    Widget* widget() const { return widget_; }
  
   private:
    // ui::CompositorObserver:
    void OnCompositingDidCommit(ui::Compositor* compositor) override {
      quit_closure_.Run();
      quit_closure_.Reset();
    }
    void OnCompositingStarted(ui::Compositor* compositor,
                              base::TimeTicks start_time) override {}
    void OnCompositingEnded(ui::Compositor* compositor) override {}
    void OnCompositingLockStateChanged(ui::Compositor* compositor) override {}
    void OnCompositingChildResizing(ui::Compositor* compositor) override {}
    void OnCompositingShuttingDown(ui::Compositor* compositor) override {}
  
    Widget* widget_ = nullptr;
  
    // Disable scrollbar hiding (i.e. disable overlay scrollbars) by default.
    bool use_overlay_scrollers_ = false;
  
    base::Closure quit_closure_;
  
  #if defined(OS_MACOSX)
    std::unique_ptr<ui::test::ScopedPreferredScrollerStyle> scroller_style_;
  #endif
  
    DISALLOW_COPY_AND_ASSIGN(WidgetScrollViewTest);
  };
  
  constexpr int WidgetScrollViewTest::kDefaultHeight;
  constexpr int WidgetScrollViewTest::kDefaultWidth;
  
  // A gtest parameter to permute over whether ScrollView uses a left-to-right or
  // right-to-left locale, or whether it uses ui::Layers or View bounds offsets to
  // position contents (i.e. features::kUiCompositorScrollWithLayers).
  enum class UiConfig { kLtr, kLtrWithLayers, kRtl, kRtlWithLayers };
  
  class WidgetScrollViewTestRTLAndLayers
      : public WidgetScrollViewTest,
        public ::testing::WithParamInterface<UiConfig> {
   public:
    WidgetScrollViewTestRTLAndLayers() : locale_(IsTestingRtl() ? "he" : "en") {
      if (IsTestingLayers()) {
        layer_config_.InitAndEnableFeature(
            features::kUiCompositorScrollWithLayers);
      } else {
        layer_config_.InitAndDisableFeature(
            features::kUiCompositorScrollWithLayers);
      }
    }
  
    bool IsTestingRtl() const {
      return GetParam() == UiConfig::kRtl ||
             GetParam() == UiConfig::kRtlWithLayers;
    }
  
    bool IsTestingLayers() const {
      return GetParam() == UiConfig::kLtrWithLayers ||
             GetParam() == UiConfig::kRtlWithLayers;
    }
  
    // Returns a point in the coordinate space of |target| by translating a point
    // inset one pixel from the top of the Widget and one pixel on the leading
    // side of the Widget. There should be no scroll bar on this side. If
    // |flip_result| is true, automatically account for flipped coordinates in
    // RTL.
    gfx::Point HitTestInCorner(View* target, bool flip_result = true) const {
      const gfx::Point test_mouse_point_in_root =
          IsTestingRtl() ? gfx::Point(kDefaultWidth - 1, 1) : gfx::Point(1, 1);
      gfx::Point point = test_mouse_point_in_root;
      View::ConvertPointToTarget(widget()->GetRootView(), target, &point);
      if (flip_result)
        return gfx::Point(target->GetMirroredXInView(point.x()), point.y());
      return point;
    }
  
   private:
    base::test::ScopedRestoreICUDefaultLocale locale_;
    base::test::ScopedFeatureList layer_config_;
  
    DISALLOW_COPY_AND_ASSIGN(WidgetScrollViewTestRTLAndLayers);
  };
  
  std::string UiConfigToString(const testing::TestParamInfo<UiConfig>& info) {
    switch (info.param) {
      case UiConfig::kLtr:
        return "LTR";
      case UiConfig::kLtrWithLayers:
        return "LTR_LAYERS";
      case UiConfig::kRtl:
        return "RTL";
      case UiConfig::kRtlWithLayers:
        return "RTL_LAYERS";
    }
    NOTREACHED();
    return std::string();
  }
  
  // Verifies the viewport is sized to fit the available space.
  TEST_F(ScrollViewTest, ViewportSizedToFit) {
    View* contents = InstallContents();
    scroll_view_->Layout();
    EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString());
  }
  
  // Verifies the viewport and content is sized to fit the available space for
  // bounded scroll view.
  TEST_F(ScrollViewTest, BoundedViewportSizedToFit) {
    View* contents = InstallContents();
    scroll_view_->ClipHeightTo(100, 200);
    scroll_view_->SetBorder(CreateSolidBorder(2, 0));
    scroll_view_->Layout();
    EXPECT_EQ("2,2 96x96", contents->parent()->bounds().ToString());
  
    // Make sure the width of |contents| is set properly not to overflow the
    // viewport.
    EXPECT_EQ(96, contents->width());
  }
  
  // Verifies that the vertical scrollbar does not unnecessarily appear for a
  // contents whose height always matches the height of the viewport.
  TEST_F(ScrollViewTest, VerticalScrollbarDoesNotAppearUnnecessarily) {
    const gfx::Rect default_outer_bounds(0, 0, 100, 100);
    View* contents = new VerticalResizingView;
    scroll_view_->SetContents(contents);
    scroll_view_->SetBoundsRect(default_outer_bounds);
    scroll_view_->Layout();
    EXPECT_FALSE(scroll_view_->vertical_scroll_bar()->visible());
    EXPECT_TRUE(scroll_view_->horizontal_scroll_bar()->visible());
  }
  
  // Verifies the scrollbars are added as necessary.
  // If on Mac, test the non-overlay scrollbars.
  TEST_F(ScrollViewTest, ScrollBars) {
    View* contents = InstallContents();
  
    // Size the contents such that vertical scrollbar is needed.
    contents->SetBounds(0, 0, 50, 400);
    scroll_view_->Layout();
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth(),
              contents->parent()->width());
    EXPECT_EQ(100, contents->parent()->height());
    CheckScrollbarVisibility(scroll_view_.get(), VERTICAL, true);
    CheckScrollbarVisibility(scroll_view_.get(), HORIZONTAL, false);
    EXPECT_TRUE(!scroll_view_->horizontal_scroll_bar() ||
                !scroll_view_->horizontal_scroll_bar()->visible());
    ASSERT_TRUE(scroll_view_->vertical_scroll_bar() != NULL);
    EXPECT_TRUE(scroll_view_->vertical_scroll_bar()->visible());
  
    // Size the contents such that horizontal scrollbar is needed.
    contents->SetBounds(0, 0, 400, 50);
    scroll_view_->Layout();
    EXPECT_EQ(100, contents->parent()->width());
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutHeight(),
              contents->parent()->height());
    CheckScrollbarVisibility(scroll_view_.get(), VERTICAL, false);
    CheckScrollbarVisibility(scroll_view_.get(), HORIZONTAL, true);
  
    // Both horizontal and vertical.
    contents->SetBounds(0, 0, 300, 400);
    scroll_view_->Layout();
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth(),
              contents->parent()->width());
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutHeight(),
              contents->parent()->height());
    CheckScrollbarVisibility(scroll_view_.get(), VERTICAL, true);
    CheckScrollbarVisibility(scroll_view_.get(), HORIZONTAL, true);
  
    // Add a border, test vertical scrollbar.
    const int kTopPadding = 1;
    const int kLeftPadding = 2;
    const int kBottomPadding = 3;
    const int kRightPadding = 4;
    scroll_view_->SetBorder(CreateEmptyBorder(kTopPadding, kLeftPadding,
                                              kBottomPadding, kRightPadding));
    contents->SetBounds(0, 0, 50, 400);
    scroll_view_->Layout();
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth() - kLeftPadding -
                  kRightPadding,
              contents->parent()->width());
    EXPECT_EQ(100 - kTopPadding - kBottomPadding, contents->parent()->height());
    EXPECT_TRUE(!scroll_view_->horizontal_scroll_bar() ||
                !scroll_view_->horizontal_scroll_bar()->visible());
    ASSERT_TRUE(scroll_view_->vertical_scroll_bar() != NULL);
    EXPECT_TRUE(scroll_view_->vertical_scroll_bar()->visible());
    gfx::Rect bounds = scroll_view_->vertical_scroll_bar()->bounds();
    EXPECT_EQ(100 - VerticalScrollBarWidth() - kRightPadding, bounds.x());
    EXPECT_EQ(100 - kRightPadding, bounds.right());
    EXPECT_EQ(kTopPadding, bounds.y());
    EXPECT_EQ(100 - kBottomPadding, bounds.bottom());
  
    // Horizontal with border.
    contents->SetBounds(0, 0, 400, 50);
    scroll_view_->Layout();
    EXPECT_EQ(100 - kLeftPadding - kRightPadding, contents->parent()->width());
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutHeight() - kTopPadding -
                  kBottomPadding,
              contents->parent()->height());
    ASSERT_TRUE(scroll_view_->horizontal_scroll_bar() != NULL);
    EXPECT_TRUE(scroll_view_->horizontal_scroll_bar()->visible());
    EXPECT_TRUE(!scroll_view_->vertical_scroll_bar() ||
                !scroll_view_->vertical_scroll_bar()->visible());
    bounds = scroll_view_->horizontal_scroll_bar()->bounds();
    EXPECT_EQ(kLeftPadding, bounds.x());
    EXPECT_EQ(100 - kRightPadding, bounds.right());
    EXPECT_EQ(100 - kBottomPadding - HorizontalScrollBarHeight(), bounds.y());
    EXPECT_EQ(100 - kBottomPadding, bounds.bottom());
  
    // Both horizontal and vertical with border.
    contents->SetBounds(0, 0, 300, 400);
    scroll_view_->Layout();
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth() - kLeftPadding -
                  kRightPadding,
              contents->parent()->width());
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutHeight() - kTopPadding -
                  kBottomPadding,
              contents->parent()->height());
    bounds = scroll_view_->horizontal_scroll_bar()->bounds();
    // Check horiz.
    ASSERT_TRUE(scroll_view_->horizontal_scroll_bar() != NULL);
    EXPECT_TRUE(scroll_view_->horizontal_scroll_bar()->visible());
    bounds = scroll_view_->horizontal_scroll_bar()->bounds();
    EXPECT_EQ(kLeftPadding, bounds.x());
    EXPECT_EQ(100 - kRightPadding - VerticalScrollBarWidth(), bounds.right());
    EXPECT_EQ(100 - kBottomPadding - HorizontalScrollBarHeight(), bounds.y());
    EXPECT_EQ(100 - kBottomPadding, bounds.bottom());
    // Check vert.
    ASSERT_TRUE(scroll_view_->vertical_scroll_bar() != NULL);
    EXPECT_TRUE(scroll_view_->vertical_scroll_bar()->visible());
    bounds = scroll_view_->vertical_scroll_bar()->bounds();
    EXPECT_EQ(100 - VerticalScrollBarWidth() - kRightPadding, bounds.x());
    EXPECT_EQ(100 - kRightPadding, bounds.right());
    EXPECT_EQ(kTopPadding, bounds.y());
    EXPECT_EQ(100 - kBottomPadding - HorizontalScrollBarHeight(),
              bounds.bottom());
  }
  
  // Assertions around adding a header.
  TEST_F(ScrollViewTest, Header) {
    CustomView* header = new CustomView;
    scroll_view_->SetHeader(header);
    View* header_parent = header->parent();
    View* contents = InstallContents();
  
    scroll_view_->Layout();
    // |header|s preferred size is empty, which should result in all space going
    // to contents.
    EXPECT_EQ("0,0 100x0", header->parent()->bounds().ToString());
    EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString());
  
    // With layered scrolling, ScrollView::Layout() will impose a size on the
    // contents that fills the viewport. Since the test view doesn't have its own
    // Layout, reset it in this case so that adding a header doesn't shift the
    // contents down and require scrollbars.
    if (contents->layer()) {
      EXPECT_EQ("0,0 100x100", contents->bounds().ToString());
      contents->SetBoundsRect(gfx::Rect());
    }
    EXPECT_EQ("0,0 0x0", contents->bounds().ToString());
  
    // Get the header a height of 20.
    header->SetPreferredSize(gfx::Size(10, 20));
    EXPECT_EQ("0,0 100x20", header->parent()->bounds().ToString());
    EXPECT_EQ("0,20 100x80", contents->parent()->bounds().ToString());
    if (contents->layer()) {
      EXPECT_EQ("0,0 100x80", contents->bounds().ToString());
      contents->SetBoundsRect(gfx::Rect());
    }
    EXPECT_EQ("0,0 0x0", contents->bounds().ToString());
  
    // Remove the header.
    scroll_view_->SetHeader(NULL);
    // SetHeader(NULL) deletes header.
    header = NULL;
    EXPECT_EQ("0,0 100x0", header_parent->bounds().ToString());
    EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString());
  }
  
  // Verifies the scrollbars are added as necessary when a header is present.
  TEST_F(ScrollViewTest, ScrollBarsWithHeader) {
    CustomView* header = new CustomView;
    scroll_view_->SetHeader(header);
    View* contents = InstallContents();
  
    header->SetPreferredSize(gfx::Size(10, 20));
  
    // Size the contents such that vertical scrollbar is needed.
    contents->SetBounds(0, 0, 50, 400);
    scroll_view_->Layout();
    EXPECT_EQ(0, contents->parent()->x());
    EXPECT_EQ(20, contents->parent()->y());
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth(),
              contents->parent()->width());
    EXPECT_EQ(80, contents->parent()->height());
    EXPECT_EQ(0, header->parent()->x());
    EXPECT_EQ(0, header->parent()->y());
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth(),
              header->parent()->width());
    EXPECT_EQ(20, header->parent()->height());
    EXPECT_TRUE(!scroll_view_->horizontal_scroll_bar() ||
                !scroll_view_->horizontal_scroll_bar()->visible());
    ASSERT_TRUE(scroll_view_->vertical_scroll_bar() != NULL);
    EXPECT_TRUE(scroll_view_->vertical_scroll_bar()->visible());
    // Make sure the vertical scrollbar overlaps the header for traditional
    // scrollbars and doesn't overlap the header for overlay scrollbars.
    const int expected_scrollbar_y =
        scroll_view_->vertical_scroll_bar()->OverlapsContent()
            ? header->bounds().bottom()
            : header->y();
    EXPECT_EQ(expected_scrollbar_y, scroll_view_->vertical_scroll_bar()->y());
    EXPECT_EQ(header->y(), contents->y());
  
    // Size the contents such that horizontal scrollbar is needed.
    contents->SetBounds(0, 0, 400, 50);
    scroll_view_->Layout();
    EXPECT_EQ(0, contents->parent()->x());
    EXPECT_EQ(20, contents->parent()->y());
    EXPECT_EQ(100, contents->parent()->width());
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutHeight() - 20,
              contents->parent()->height());
    EXPECT_EQ(0, header->parent()->x());
    EXPECT_EQ(0, header->parent()->y());
    EXPECT_EQ(100, header->parent()->width());
    EXPECT_EQ(20, header->parent()->height());
    ASSERT_TRUE(scroll_view_->horizontal_scroll_bar() != NULL);
    EXPECT_TRUE(scroll_view_->horizontal_scroll_bar()->visible());
    EXPECT_TRUE(!scroll_view_->vertical_scroll_bar() ||
                !scroll_view_->vertical_scroll_bar()->visible());
  
    // Both horizontal and vertical.
    contents->SetBounds(0, 0, 300, 400);
    scroll_view_->Layout();
    EXPECT_EQ(0, contents->parent()->x());
    EXPECT_EQ(20, contents->parent()->y());
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth(),
              contents->parent()->width());
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutHeight() - 20,
              contents->parent()->height());
    EXPECT_EQ(0, header->parent()->x());
    EXPECT_EQ(0, header->parent()->y());
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth(),
              header->parent()->width());
    EXPECT_EQ(20, header->parent()->height());
    ASSERT_TRUE(scroll_view_->horizontal_scroll_bar() != NULL);
    EXPECT_TRUE(scroll_view_->horizontal_scroll_bar()->visible());
    ASSERT_TRUE(scroll_view_->vertical_scroll_bar() != NULL);
    EXPECT_TRUE(scroll_view_->vertical_scroll_bar()->visible());
  }
  
  // Verifies the header scrolls horizontally with the content.
  TEST_F(ScrollViewTest, HeaderScrollsWithContent) {
    ScrollViewTestApi test_api(scroll_view_.get());
    CustomView* contents = new CustomView;
    scroll_view_->SetContents(contents);
    contents->SetPreferredSize(gfx::Size(500, 500));
  
    CustomView* header = new CustomView;
    scroll_view_->SetHeader(header);
    header->SetPreferredSize(gfx::Size(500, 20));
  
    scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 100, 100));
    EXPECT_EQ("0,0", test_api.IntegralViewOffset().ToString());
    EXPECT_EQ("0,0", header->origin().ToString());
  
    // Scroll the horizontal scrollbar.
    ASSERT_TRUE(scroll_view_->horizontal_scroll_bar());
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL), 1);
    EXPECT_EQ("-1,0", test_api.IntegralViewOffset().ToString());
    EXPECT_EQ("-1,0", header->origin().ToString());
  
    // Scrolling the vertical scrollbar shouldn't effect the header.
    ASSERT_TRUE(scroll_view_->vertical_scroll_bar());
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), 1);
    EXPECT_EQ("-1,-1", test_api.IntegralViewOffset().ToString());
    EXPECT_EQ("-1,0", header->origin().ToString());
  }
  
  // Verifies ScrollRectToVisible() on the child works.
  TEST_F(ScrollViewTest, ScrollRectToVisible) {
    ScrollViewTestApi test_api(scroll_view_.get());
    CustomView* contents = new CustomView;
    scroll_view_->SetContents(contents);
    contents->SetPreferredSize(gfx::Size(500, 1000));
  
    scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 100, 100));
    scroll_view_->Layout();
    EXPECT_EQ("0,0", test_api.IntegralViewOffset().ToString());
  
    // Scroll to y=405 height=10, this should make the y position of the content
    // at (405 + 10) - viewport_height (scroll region bottom aligned).
    contents->ScrollRectToVisible(gfx::Rect(0, 405, 10, 10));
    const int viewport_height = test_api.contents_viewport()->height();
  
    // Expect there to be a horizontal scrollbar, making the viewport shorter.
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutHeight(), viewport_height);
  
    gfx::ScrollOffset offset = test_api.CurrentOffset();
    EXPECT_EQ(415 - viewport_height, offset.y());
  
    // Scroll to the current y-location and 10x10; should do nothing.
    contents->ScrollRectToVisible(gfx::Rect(0, offset.y(), 10, 10));
    EXPECT_EQ(415 - viewport_height, test_api.CurrentOffset().y());
  }
  
  // Verifies that child scrolls into view when it's focused.
  TEST_F(ScrollViewTest, ScrollChildToVisibleOnFocus) {
    ScrollViewTestApi test_api(scroll_view_.get());
    CustomView* contents = new CustomView;
    scroll_view_->SetContents(contents);
    contents->SetPreferredSize(gfx::Size(500, 1000));
    FixedView* child = new FixedView;
    child->SetPreferredSize(gfx::Size(10, 10));
    child->SetPosition(gfx::Point(0, 405));
    contents->AddChildView(child);
  
    scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 100, 100));
    scroll_view_->Layout();
    EXPECT_EQ(gfx::Point(), test_api.IntegralViewOffset());
  
    // Set focus to the child control. This should cause the control to scroll to
    // y=405 height=10. Like the above test, this should make the y position of
    // the content at (405 + 10) - viewport_height (scroll region bottom aligned).
    child->SetFocus();
    const int viewport_height = test_api.contents_viewport()->height();
  
    // Expect there to be a horizontal scrollbar, making the viewport shorter.
    EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutHeight(), viewport_height);
  
    gfx::ScrollOffset offset = test_api.CurrentOffset();
    EXPECT_EQ(415 - viewport_height, offset.y());
  }
  
  // Verifies ClipHeightTo() uses the height of the content when it is between the
  // minimum and maximum height values.
  TEST_F(ScrollViewTest, ClipHeightToNormalContentHeight) {
    scroll_view_->ClipHeightTo(kMinHeight, kMaxHeight);
  
    const int kNormalContentHeight = 75;
    scroll_view_->SetContents(
        new views::StaticSizedView(gfx::Size(kWidth, kNormalContentHeight)));
  
    EXPECT_EQ(gfx::Size(kWidth, kNormalContentHeight),
              scroll_view_->GetPreferredSize());
  
    scroll_view_->SizeToPreferredSize();
    scroll_view_->Layout();
  
    EXPECT_EQ(gfx::Size(kWidth, kNormalContentHeight),
              scroll_view_->contents()->size());
    EXPECT_EQ(gfx::Size(kWidth, kNormalContentHeight), scroll_view_->size());
  }
  
  // Verifies ClipHeightTo() uses the minimum height when the content is shorter
  // than the minimum height value.
  TEST_F(ScrollViewTest, ClipHeightToShortContentHeight) {
    scroll_view_->ClipHeightTo(kMinHeight, kMaxHeight);
  
    const int kShortContentHeight = 10;
    View* contents =
        new views::StaticSizedView(gfx::Size(kWidth, kShortContentHeight));
    scroll_view_->SetContents(contents);
  
    EXPECT_EQ(gfx::Size(kWidth, kMinHeight), scroll_view_->GetPreferredSize());
  
    scroll_view_->SizeToPreferredSize();
    scroll_view_->Layout();
  
    // Layered scrolling requires the contents to fill the viewport.
    if (contents->layer()) {
      EXPECT_EQ(gfx::Size(kWidth, kMinHeight), scroll_view_->contents()->size());
    } else {
      EXPECT_EQ(gfx::Size(kWidth, kShortContentHeight),
                scroll_view_->contents()->size());
    }
    EXPECT_EQ(gfx::Size(kWidth, kMinHeight), scroll_view_->size());
  }
  
  // Verifies ClipHeightTo() uses the maximum height when the content is longer
  // thamn the maximum height value.
  TEST_F(ScrollViewTest, ClipHeightToTallContentHeight) {
    scroll_view_->ClipHeightTo(kMinHeight, kMaxHeight);
  
    const int kTallContentHeight = 1000;
    scroll_view_->SetContents(
        new views::StaticSizedView(gfx::Size(kWidth, kTallContentHeight)));
  
    EXPECT_EQ(gfx::Size(kWidth, kMaxHeight), scroll_view_->GetPreferredSize());
  
    scroll_view_->SizeToPreferredSize();
    scroll_view_->Layout();
  
    // The width may be less than kWidth if the scroll bar takes up some width.
    EXPECT_GE(kWidth, scroll_view_->contents()->width());
    EXPECT_EQ(kTallContentHeight, scroll_view_->contents()->height());
    EXPECT_EQ(gfx::Size(kWidth, kMaxHeight), scroll_view_->size());
  }
  
  // Verifies that when ClipHeightTo() produces a scrollbar, it reduces the width
  // of the inner content of the ScrollView.
  TEST_F(ScrollViewTest, ClipHeightToScrollbarUsesWidth) {
    scroll_view_->ClipHeightTo(kMinHeight, kMaxHeight);
  
    // Create a view that will be much taller than it is wide.
    scroll_view_->SetContents(new views::ProportionallySizedView(1000));
  
    // Without any width, it will default to 0,0 but be overridden by min height.
    scroll_view_->SizeToPreferredSize();
    EXPECT_EQ(gfx::Size(0, kMinHeight), scroll_view_->GetPreferredSize());
  
    gfx::Size new_size(kWidth, scroll_view_->GetHeightForWidth(kWidth));
    scroll_view_->SetSize(new_size);
    scroll_view_->Layout();
  
    int expected_width = kWidth - scroll_view_->GetScrollBarLayoutWidth();
    EXPECT_EQ(scroll_view_->contents()->size().width(), expected_width);
    EXPECT_EQ(scroll_view_->contents()->size().height(), 1000 * expected_width);
    EXPECT_EQ(gfx::Size(kWidth, kMaxHeight), scroll_view_->size());
  }
  
  TEST_F(ScrollViewTest, CornerViewVisibility) {
    View* contents = InstallContents();
    View* corner_view = ScrollViewTestApi(scroll_view_.get()).corner_view();
  
    contents->SetBounds(0, 0, 200, 200);
    scroll_view_->Layout();
  
    // Corner view should not exist if using overlay scrollbars.
    if (scroll_view_->vertical_scroll_bar()->OverlapsContent()) {
      EXPECT_FALSE(corner_view->parent());
      return;
    }
  
    // Corner view should be visible when both scrollbars are visible.
    EXPECT_EQ(scroll_view_.get(), corner_view->parent());
    EXPECT_TRUE(corner_view->visible());
  
    // Corner view should be aligned to the scrollbars.
    EXPECT_EQ(scroll_view_->vertical_scroll_bar()->x(), corner_view->x());
    EXPECT_EQ(scroll_view_->horizontal_scroll_bar()->y(), corner_view->y());
    EXPECT_EQ(scroll_view_->GetScrollBarLayoutWidth(), corner_view->width());
    EXPECT_EQ(scroll_view_->GetScrollBarLayoutHeight(), corner_view->height());
  
    // Corner view should be removed when only the vertical scrollbar is visible.
    contents->SetBounds(0, 0, 50, 200);
    scroll_view_->Layout();
    EXPECT_FALSE(corner_view->parent());
  
    // ... or when only the horizontal scrollbar is visible.
    contents->SetBounds(0, 0, 200, 50);
    scroll_view_->Layout();
    EXPECT_FALSE(corner_view->parent());
  
    // ... or when no scrollbar is visible.
    contents->SetBounds(0, 0, 50, 50);
    scroll_view_->Layout();
    EXPECT_FALSE(corner_view->parent());
  
    // Corner view should reappear when both scrollbars reappear.
    contents->SetBounds(0, 0, 200, 200);
    scroll_view_->Layout();
    EXPECT_EQ(scroll_view_.get(), corner_view->parent());
    EXPECT_TRUE(corner_view->visible());
  }
  
  TEST_F(ScrollViewTest, ChildWithLayerTest) {
    View* contents = InstallContents();
    ScrollViewTestApi test_api(scroll_view_.get());
  
    if (test_api.contents_viewport()->layer())
      return;
  
    View* child = new View();
    contents->AddChildView(child);
    child->SetPaintToLayer(ui::LAYER_TEXTURED);
  
    ASSERT_TRUE(test_api.contents_viewport()->layer());
    // The default ScrollView color is opaque, so that fills bounds opaquely
    // should be true.
    EXPECT_TRUE(test_api.contents_viewport()->layer()->fills_bounds_opaquely());
  
    // Setting a transparent color should make fills opaquely false.
    scroll_view_->SetBackgroundColor(SK_ColorTRANSPARENT);
    EXPECT_FALSE(test_api.contents_viewport()->layer()->fills_bounds_opaquely());
  
    child->DestroyLayer();
    EXPECT_FALSE(test_api.contents_viewport()->layer());
  
    View* child_child = new View();
    child->AddChildView(child_child);
    EXPECT_FALSE(test_api.contents_viewport()->layer());
    child->SetPaintToLayer(ui::LAYER_TEXTURED);
    EXPECT_TRUE(test_api.contents_viewport()->layer());
  }
  
  // Validates that if a child of a ScrollView adds a layer, then a layer
  // is added to the ScrollView's viewport.
  TEST_F(ScrollViewTest, DontCreateLayerOnViewportIfLayerOnScrollViewCreated) {
    View* contents = InstallContents();
    ScrollViewTestApi test_api(scroll_view_.get());
  
    if (test_api.contents_viewport()->layer())
      return;
  
    scroll_view_->SetPaintToLayer();
  
    View* child = new View();
    contents->AddChildView(child);
    child->SetPaintToLayer(ui::LAYER_TEXTURED);
  
    EXPECT_FALSE(test_api.contents_viewport()->layer());
  }
  
  #if defined(OS_MACOSX)
  // Tests the overlay scrollbars on Mac. Ensure that they show up properly and
  // do not overlap each other.
  TEST_F(ScrollViewTest, CocoaOverlayScrollBars) {
    SetOverlayScrollersEnabled(true);
    View* contents = InstallContents();
  
    // Size the contents such that vertical scrollbar is needed.
    // Since it is overlaid, the ViewPort size should match the ScrollView.
    contents->SetBounds(0, 0, 50, 400);
    scroll_view_->Layout();
    EXPECT_EQ(100, contents->parent()->width());
    EXPECT_EQ(100, contents->parent()->height());
    EXPECT_EQ(0, scroll_view_->GetScrollBarLayoutWidth());
    CheckScrollbarVisibility(scroll_view_.get(), VERTICAL, true);
    CheckScrollbarVisibility(scroll_view_.get(), HORIZONTAL, false);
  
    // Size the contents such that horizontal scrollbar is needed.
    contents->SetBounds(0, 0, 400, 50);
    scroll_view_->Layout();
    EXPECT_EQ(100, contents->parent()->width());
    EXPECT_EQ(100, contents->parent()->height());
    EXPECT_EQ(0, scroll_view_->GetScrollBarLayoutHeight());
    CheckScrollbarVisibility(scroll_view_.get(), VERTICAL, false);
    CheckScrollbarVisibility(scroll_view_.get(), HORIZONTAL, true);
  
    // Both horizontal and vertical scrollbars.
    contents->SetBounds(0, 0, 300, 400);
    scroll_view_->Layout();
    EXPECT_EQ(100, contents->parent()->width());
    EXPECT_EQ(100, contents->parent()->height());
    EXPECT_EQ(0, scroll_view_->GetScrollBarLayoutWidth());
    EXPECT_EQ(0, scroll_view_->GetScrollBarLayoutHeight());
    CheckScrollbarVisibility(scroll_view_.get(), VERTICAL, true);
    CheckScrollbarVisibility(scroll_view_.get(), HORIZONTAL, true);
  
    // Make sure the horizontal and vertical scrollbars don't overlap each other.
    gfx::Rect vert_bounds = scroll_view_->vertical_scroll_bar()->bounds();
    gfx::Rect horiz_bounds = scroll_view_->horizontal_scroll_bar()->bounds();
    EXPECT_EQ(vert_bounds.x(), horiz_bounds.right());
    EXPECT_EQ(horiz_bounds.y(), vert_bounds.bottom());
  
    // Switch to the non-overlay style and check that the ViewPort is now sized
    // to be smaller, and ScrollbarWidth and ScrollbarHeight are non-zero.
    SetOverlayScrollersEnabled(false);
    EXPECT_EQ(100 - VerticalScrollBarWidth(), contents->parent()->width());
    EXPECT_EQ(100 - HorizontalScrollBarHeight(), contents->parent()->height());
    EXPECT_NE(0, VerticalScrollBarWidth());
    EXPECT_NE(0, HorizontalScrollBarHeight());
  }
  
  // Test overlay scrollbar behavior when just resting fingers on the trackpad.
  TEST_F(WidgetScrollViewTest, ScrollersOnRest) {
    // Allow expectations to distinguish between fade outs and immediate changes.
    ui::ScopedAnimationDurationScaleMode really_animate(
        ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  
    const float kMaxOpacity = 0.8f;  // Constant from cocoa_scroll_bar.mm.
  
    SetUseOverlayScrollers();
  
    // Set up with both scrollers.
    ScrollView* scroll_view = AddScrollViewWithContentSize(
        gfx::Size(kDefaultWidth * 5, kDefaultHeight * 5));
    ScrollViewTestApi test_api(scroll_view);
    BaseScrollBar* bar[]{test_api.GetBaseScrollBar(HORIZONTAL),
                         test_api.GetBaseScrollBar(VERTICAL)};
    base::Timer* hide_timer[]{test_api.GetScrollBarHideTimer(HORIZONTAL),
                              test_api.GetScrollBarHideTimer(VERTICAL)};
  
    EXPECT_EQ(0, bar[HORIZONTAL]->layer()->opacity());
    EXPECT_EQ(0, bar[VERTICAL]->layer()->opacity());
  
    ui::test::EventGenerator generator(
        GetContext(), scroll_view->GetWidget()->GetNativeWindow());
  
    generator.GenerateTrackpadRest();
    // Scrollers should be max opacity without an animation.
    EXPECT_EQ(kMaxOpacity, bar[HORIZONTAL]->layer()->opacity());
    EXPECT_EQ(kMaxOpacity, bar[VERTICAL]->layer()->opacity());
    EXPECT_FALSE(hide_timer[HORIZONTAL]->IsRunning());
    EXPECT_FALSE(hide_timer[VERTICAL]->IsRunning());
  
    generator.CancelTrackpadRest();
    // Scrollers should start fading out, but only after a delay.
    for (ScrollBarOrientation orientation : {HORIZONTAL, VERTICAL}) {
      EXPECT_EQ(kMaxOpacity, bar[orientation]->layer()->GetTargetOpacity());
      EXPECT_TRUE(hide_timer[orientation]->IsRunning());
      // Trigger the timer. Should then be fading out.
      hide_timer[orientation]->user_task().Run();
      hide_timer[orientation]->Stop();
      EXPECT_EQ(0, bar[orientation]->layer()->GetTargetOpacity());
    }
  
    // Rest again.
    generator.GenerateTrackpadRest();
    EXPECT_EQ(kMaxOpacity, bar[HORIZONTAL]->layer()->GetTargetOpacity());
    EXPECT_EQ(kMaxOpacity, bar[VERTICAL]->layer()->GetTargetOpacity());
  
    // Scroll vertically.
    const float y_offset = 3;
    const int kSteps = 1;
    const int kNnumFingers = 2;
    generator.ScrollSequence(generator.current_location(), base::TimeDelta(), 0,
                             y_offset, kSteps, kNnumFingers);
  
    // Horizontal scroller should start fading out immediately.
    EXPECT_EQ(kMaxOpacity, bar[HORIZONTAL]->layer()->opacity());
    EXPECT_EQ(0, bar[HORIZONTAL]->layer()->GetTargetOpacity());
    EXPECT_FALSE(hide_timer[HORIZONTAL]->IsRunning());
  
    // Vertical should remain visible, but ready to fade out after a delay.
    EXPECT_EQ(kMaxOpacity, bar[VERTICAL]->layer()->opacity());
    EXPECT_EQ(kMaxOpacity, bar[VERTICAL]->layer()->GetTargetOpacity());
    EXPECT_TRUE(hide_timer[VERTICAL]->IsRunning());
  
    // Scrolling should have occurred.
    EXPECT_EQ(gfx::ScrollOffset(0, y_offset), test_api.CurrentOffset());
  
    // Then, scrolling horizontally should show the horizontal scroller. The
    // vertical scroller should still be visible, running its hide timer.
    const float x_offset = 5;
    generator.ScrollSequence(generator.current_location(), base::TimeDelta(),
                             x_offset, 0, kSteps, kNnumFingers);
    for (ScrollBarOrientation orientation : {HORIZONTAL, VERTICAL}) {
      EXPECT_EQ(kMaxOpacity, bar[orientation]->layer()->opacity());
      EXPECT_EQ(kMaxOpacity, bar[orientation]->layer()->GetTargetOpacity());
      EXPECT_TRUE(hide_timer[orientation]->IsRunning());
    }
  
    // Now scrolling has occurred in both directions.
    EXPECT_EQ(gfx::ScrollOffset(x_offset, y_offset), test_api.CurrentOffset());
  }
  
  #endif  // OS_MACOSX
  
  // Test that increasing the size of the viewport "below" scrolled content causes
  // the content to scroll up so that it still fills the viewport.
  TEST_F(ScrollViewTest, ConstrainScrollToBounds) {
    ScrollViewTestApi test_api(scroll_view_.get());
  
    View* contents = InstallContents();
    contents->SetBoundsRect(gfx::Rect(0, 0, 300, 300));
    scroll_view_->Layout();
  
    EXPECT_EQ(gfx::ScrollOffset(), test_api.CurrentOffset());
  
    // Scroll as far as it goes and query location to discount scroll bars.
    contents->ScrollRectToVisible(gfx::Rect(300, 300, 1, 1));
    const gfx::ScrollOffset fully_scrolled = test_api.CurrentOffset();
    EXPECT_NE(gfx::ScrollOffset(), fully_scrolled);
  
    // Making the viewport 55 pixels taller should scroll up the same amount.
    scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 100, 155));
    scroll_view_->Layout();
    EXPECT_EQ(fully_scrolled.y() - 55, test_api.CurrentOffset().y());
    EXPECT_EQ(fully_scrolled.x(), test_api.CurrentOffset().x());
  
    // And 77 pixels wider should scroll left. Also make it short again: the y-
    // offset from the last change should remain.
    scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 177, 100));
    scroll_view_->Layout();
    EXPECT_EQ(fully_scrolled.y() - 55, test_api.CurrentOffset().y());
    EXPECT_EQ(fully_scrolled.x() - 77, test_api.CurrentOffset().x());
  }
  
  // Calling Layout on ScrollView should not reset the scroll location.
  TEST_F(ScrollViewTest, ContentScrollNotResetOnLayout) {
    ScrollViewTestApi test_api(scroll_view_.get());
  
    CustomView* contents = new CustomView;
    contents->SetPreferredSize(gfx::Size(300, 300));
    scroll_view_->SetContents(contents);
    scroll_view_->ClipHeightTo(0, 150);
    scroll_view_->SizeToPreferredSize();
    // ScrollView preferred width matches that of |contents|, with the height
    // capped at the value we clipped to.
    EXPECT_EQ(gfx::Size(300, 150), scroll_view_->size());
  
    // Scroll down.
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), 25);
    EXPECT_EQ(25, test_api.CurrentOffset().y());
    // Call Layout; no change to scroll position.
    scroll_view_->Layout();
    EXPECT_EQ(25, test_api.CurrentOffset().y());
    // Change contents of |contents|, call Layout; still no change to scroll
    // position.
    contents->SetPreferredSize(gfx::Size(300, 500));
    contents->InvalidateLayout();
    scroll_view_->Layout();
    EXPECT_EQ(25, test_api.CurrentOffset().y());
  
    // Change |contents| to be shorter than the ScrollView's clipped height.
    // This /will/ change the scroll location due to ConstrainScrollToBounds.
    contents->SetPreferredSize(gfx::Size(300, 50));
    scroll_view_->Layout();
    EXPECT_EQ(0, test_api.CurrentOffset().y());
  }
  
  // Test that overflow indicators turn on appropriately.
  TEST_F(ScrollViewTest, VerticalOverflowIndicators) {
    const int kWidth = 100;
  
    ScrollViewTestApi test_api(scroll_view_.get());
  
    // Set up with vertical scrollbar.
    FixedView* contents = new FixedView;
    contents->SetPreferredSize(gfx::Size(kWidth, kMaxHeight * 5));
    scroll_view_->SetContents(contents);
    scroll_view_->ClipHeightTo(0, kMaxHeight);
  
    // Make sure the size is set such that no horizontal scrollbar gets shown.
    scroll_view_->SetSize(
        gfx::Size(kWidth + test_api.GetBaseScrollBar(VERTICAL)->GetThickness(),
                  kMaxHeight));
  
    // Make sure the initial origin is 0,0
    EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
  
    // The vertical scroll bar should be visible and the horizontal scroll bar
    // should not.
    CheckScrollbarVisibility(scroll_view_.get(), VERTICAL, true);
    CheckScrollbarVisibility(scroll_view_.get(), HORIZONTAL, false);
  
    // The overflow indicator on the bottom should be visible.
    EXPECT_TRUE(test_api.more_content_bottom()->visible());
  
    // The overflow indicator on the top should not be visible.
    EXPECT_FALSE(test_api.more_content_top()->visible());
  
    // No other overflow indicators should be visible.
    EXPECT_FALSE(test_api.more_content_left()->visible());
    EXPECT_FALSE(test_api.more_content_right()->visible());
  
    // Now scroll the view to someplace in the middle of the scrollable region.
    int offset = kMaxHeight * 2;
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset);
    EXPECT_EQ(gfx::ScrollOffset(0, offset), test_api.CurrentOffset());
  
    // At this point, both overflow indicators on the top and bottom should be
    // visible.
    EXPECT_TRUE(test_api.more_content_top()->visible());
    EXPECT_TRUE(test_api.more_content_bottom()->visible());
  
    // The left and right overflow indicators should still not be visible.
    EXPECT_FALSE(test_api.more_content_left()->visible());
    EXPECT_FALSE(test_api.more_content_right()->visible());
  
    // Finally scroll the view to end of the scrollable region.
    offset = kMaxHeight * 4;
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset);
    EXPECT_EQ(gfx::ScrollOffset(0, offset), test_api.CurrentOffset());
  
    // The overflow indicator on the bottom should not be visible.
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
  
    // The overflow indicator on the top should be visible.
    EXPECT_TRUE(test_api.more_content_top()->visible());
  
    // As above, no other overflow indicators should be visible.
    EXPECT_FALSE(test_api.more_content_left()->visible());
    EXPECT_FALSE(test_api.more_content_right()->visible());
  }
  
  TEST_F(ScrollViewTest, HorizontalOverflowIndicators) {
    const int kWidth = 100;
    const int kHeight = 100;
  
    ScrollViewTestApi test_api(scroll_view_.get());
  
    // Set up with horizontal scrollbar.
    FixedView* contents = new FixedView;
    contents->SetPreferredSize(gfx::Size(kWidth * 5, kHeight));
    scroll_view_->SetContents(contents);
  
    // Make sure the size is set such that no vertical scrollbar gets shown.
    scroll_view_->SetSize(gfx::Size(
        kWidth, kHeight + test_api.GetBaseScrollBar(HORIZONTAL)->GetThickness()));
  
    contents->SetBounds(0, 0, kWidth * 5, kHeight);
  
    // Make sure the initial origin is 0,0
    EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
  
    // The horizontal scroll bar should be visible and the vertical scroll bar
    // should not.
    CheckScrollbarVisibility(scroll_view_.get(), HORIZONTAL, true);
    CheckScrollbarVisibility(scroll_view_.get(), VERTICAL, false);
  
    // The overflow indicator on the right should be visible.
    EXPECT_TRUE(test_api.more_content_right()->visible());
  
    // The overflow indicator on the left should not be visible.
    EXPECT_FALSE(test_api.more_content_left()->visible());
  
    // No other overflow indicators should be visible.
    EXPECT_FALSE(test_api.more_content_top()->visible());
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
  
    // Now scroll the view to someplace in the middle of the scrollable region.
    int offset = kWidth * 2;
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL), offset);
    EXPECT_EQ(gfx::ScrollOffset(offset, 0), test_api.CurrentOffset());
  
    // At this point, both overflow indicators on the left and right should be
    // visible.
    EXPECT_TRUE(test_api.more_content_left()->visible());
    EXPECT_TRUE(test_api.more_content_right()->visible());
  
    // The top and bottom overflow indicators should still not be visible.
    EXPECT_FALSE(test_api.more_content_top()->visible());
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
  
    // Finally scroll the view to end of the scrollable region.
    offset = kWidth * 4;
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL), offset);
    EXPECT_EQ(gfx::ScrollOffset(offset, 0), test_api.CurrentOffset());
  
    // The overflow indicator on the right should not be visible.
    EXPECT_FALSE(test_api.more_content_right()->visible());
  
    // The overflow indicator on the left should be visible.
    EXPECT_TRUE(test_api.more_content_left()->visible());
  
    // As above, no other overflow indicators should be visible.
    EXPECT_FALSE(test_api.more_content_top()->visible());
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
  }
  
  TEST_F(ScrollViewTest, HorizontalVerticalOverflowIndicators) {
    const int kWidth = 100;
    const int kHeight = 100;
  
    ScrollViewTestApi test_api(scroll_view_.get());
  
    // Set up with both horizontal and vertical scrollbars.
    FixedView* contents = new FixedView;
    contents->SetPreferredSize(gfx::Size(kWidth * 5, kHeight * 5));
    scroll_view_->SetContents(contents);
  
    // Make sure the size is set such that both scrollbars are shown.
    scroll_view_->SetSize(gfx::Size(kWidth, kHeight));
  
    // Make sure the initial origin is 0,0
    EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
  
    // The horizontal and vertical scroll bars should be visible.
    CheckScrollbarVisibility(scroll_view_.get(), HORIZONTAL, true);
    CheckScrollbarVisibility(scroll_view_.get(), VERTICAL, true);
  
    // The overflow indicators on the right and bottom should not be visible since
    // they are against the scrollbars.
    EXPECT_FALSE(test_api.more_content_right()->visible());
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
  
    // The overflow indicators on the left and top should not be visible.
    EXPECT_FALSE(test_api.more_content_left()->visible());
    EXPECT_FALSE(test_api.more_content_top()->visible());
  
    // Now scroll the view to someplace in the middle of the horizontal scrollable
    // region.
    int offset_x = kWidth * 2;
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL),
                                   offset_x);
    EXPECT_EQ(gfx::ScrollOffset(offset_x, 0), test_api.CurrentOffset());
  
    // Since there is a vertical scrollbar only the overflow indicator on the left
    // should be visible and the one on the right should still not be visible.
    EXPECT_TRUE(test_api.more_content_left()->visible());
    EXPECT_FALSE(test_api.more_content_right()->visible());
  
    // The top and bottom overflow indicators should still not be visible.
    EXPECT_FALSE(test_api.more_content_top()->visible());
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
  
    // Next, scroll the view to end of the scrollable region.
    offset_x = kWidth * 4;
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL),
                                   offset_x);
    EXPECT_EQ(gfx::ScrollOffset(offset_x, 0), test_api.CurrentOffset());
  
    // The overflow indicator on the right should still not be visible.
    EXPECT_FALSE(test_api.more_content_right()->visible());
  
    // The overflow indicator on the left should be visible.
    EXPECT_TRUE(test_api.more_content_left()->visible());
  
    // As above, the other overflow indicators should not be visible because the
    // view hasn't scrolled vertically and the bottom indicator is against the
    // horizontal scrollbar.
    EXPECT_FALSE(test_api.more_content_top()->visible());
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
  
    // Return the view back to the horizontal origin.
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL), 0);
    EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
  
    // The overflow indicators on the right and bottom should not be visible since
    // they are against the scrollbars.
    EXPECT_FALSE(test_api.more_content_right()->visible());
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
  
    // The overflow indicators on the left and top should not be visible since the
    // is at the origin.
    EXPECT_FALSE(test_api.more_content_left()->visible());
    EXPECT_FALSE(test_api.more_content_top()->visible());
  
    // Now scroll the view to somplace in the middle of the vertical scrollable
    // region.
    int offset_y = kHeight * 2;
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset_y);
    EXPECT_EQ(gfx::ScrollOffset(0, offset_y), test_api.CurrentOffset());
  
    // Similar to the above, since there is a horizontal scrollbar only the
    // overflow indicator on the top should be visible and the one on the bottom
    // should still not be visible.
    EXPECT_TRUE(test_api.more_content_top()->visible());
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
  
    // The left and right overflow indicators should still not be visible.
    EXPECT_FALSE(test_api.more_content_left()->visible());
    EXPECT_FALSE(test_api.more_content_right()->visible());
  
    // Finally, for the vertical test scroll the region all the way to the end.
    offset_y = kHeight * 4;
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset_y);
    EXPECT_EQ(gfx::ScrollOffset(0, offset_y), test_api.CurrentOffset());
  
    // The overflow indicator on the bottom should still not be visible.
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
  
    // The overflow indicator on the top should still be visible.
    EXPECT_TRUE(test_api.more_content_top()->visible());
  
    // As above, the other overflow indicators should not be visible because the
    // view hasn't scrolled horizontally and the right indicator is against the
    // vertical scrollbar.
    EXPECT_FALSE(test_api.more_content_left()->visible());
    EXPECT_FALSE(test_api.more_content_right()->visible());
  
    // Back to the horizontal. Scroll all the way to the end in the horizontal
    // direction.
    offset_x = kWidth * 4;
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL),
                                   offset_x);
    EXPECT_EQ(gfx::ScrollOffset(offset_x, offset_y), test_api.CurrentOffset());
  
    // The overflow indicator on the bottom and right should still not be visible.
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
    EXPECT_FALSE(test_api.more_content_right()->visible());
  
    // The overflow indicators on the top and left should now be visible.
    EXPECT_TRUE(test_api.more_content_top()->visible());
    EXPECT_TRUE(test_api.more_content_left()->visible());
  }
  
  TEST_F(ScrollViewTest, VerticalWithHeaderOverflowIndicators) {
    const int kWidth = 100;
  
    ScrollViewTestApi test_api(scroll_view_.get());
  
    // Set up with vertical scrollbar and a header.
    FixedView* contents = new FixedView;
    CustomView* header = new CustomView;
    contents->SetPreferredSize(gfx::Size(kWidth, kMaxHeight * 5));
    scroll_view_->SetContents(contents);
    header->SetPreferredSize(gfx::Size(10, 20));
    scroll_view_->SetHeader(header);
    scroll_view_->ClipHeightTo(0, kMaxHeight + header->height());
  
    // Make sure the size is set such that no horizontal scrollbar gets shown.
    scroll_view_->SetSize(
        gfx::Size(kWidth + test_api.GetBaseScrollBar(VERTICAL)->GetThickness(),
                  kMaxHeight + header->height()));
  
    // Make sure the initial origin is 0,0
    EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
  
    // The vertical scroll bar should be visible and the horizontal scroll bar
    // should not.
    CheckScrollbarVisibility(scroll_view_.get(), VERTICAL, true);
    CheckScrollbarVisibility(scroll_view_.get(), HORIZONTAL, false);
  
    // The overflow indicator on the bottom should be visible.
    EXPECT_TRUE(test_api.more_content_bottom()->visible());
  
    // The overflow indicator on the top should not be visible.
    EXPECT_FALSE(test_api.more_content_top()->visible());
  
    // No other overflow indicators should be visible.
    EXPECT_FALSE(test_api.more_content_left()->visible());
    EXPECT_FALSE(test_api.more_content_right()->visible());
  
    // Now scroll the view to someplace in the middle of the scrollable region.
    int offset = kMaxHeight * 2;
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset);
    EXPECT_EQ(gfx::ScrollOffset(0, offset), test_api.CurrentOffset());
  
    // At this point, only the overflow indicator on the bottom should be visible
    // because the top indicator never comes on because of the presence of the
    // header.
    EXPECT_FALSE(test_api.more_content_top()->visible());
    EXPECT_TRUE(test_api.more_content_bottom()->visible());
  
    // The left and right overflow indicators should still not be visible.
    EXPECT_FALSE(test_api.more_content_left()->visible());
    EXPECT_FALSE(test_api.more_content_right()->visible());
  
    // Finally scroll the view to end of the scrollable region.
    offset = test_api.GetBaseScrollBar(VERTICAL)->GetMaxPosition();
    scroll_view_->ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset);
    EXPECT_EQ(gfx::ScrollOffset(0, offset), test_api.CurrentOffset());
  
    // The overflow indicator on the bottom should not be visible now.
    EXPECT_FALSE(test_api.more_content_bottom()->visible());
  
    // The overflow indicator on the top should still not be visible.
    EXPECT_FALSE(test_api.more_content_top()->visible());
  
    // As above, no other overflow indicators should be visible.
    EXPECT_FALSE(test_api.more_content_left()->visible());
    EXPECT_FALSE(test_api.more_content_right()->visible());
  }
  
  // Test scrolling behavior when clicking on the scroll track.
  TEST_F(WidgetScrollViewTest, ScrollTrackScrolling) {
    // Set up with a vertical scroller.
    ScrollView* scroll_view =
        AddScrollViewWithContentSize(gfx::Size(10, kDefaultHeight * 5));
    ScrollViewTestApi test_api(scroll_view);
    BaseScrollBar* scroll_bar = test_api.GetBaseScrollBar(VERTICAL);
    View* thumb = test_api.GetScrollBarThumb(VERTICAL);
  
    // Click in the middle of the track, ensuring it's below the thumb.
    const gfx::Point location = scroll_bar->bounds().CenterPoint();
    EXPECT_GT(location.y(), thumb->bounds().bottom());
    ui::MouseEvent press(TestLeftMouseAt(location, ui::ET_MOUSE_PRESSED));
    ui::MouseEvent release(TestLeftMouseAt(location, ui::ET_MOUSE_RELEASED));
  
    const base::Timer& timer = test_api.GetScrollBarTimer(VERTICAL);
    EXPECT_FALSE(timer.IsRunning());
  
    EXPECT_EQ(0, scroll_view->GetVisibleRect().y());
    scroll_bar->OnMouseEvent(&press);
  
    // Clicking the scroll track should scroll one "page".
    EXPECT_EQ(kDefaultHeight, scroll_view->GetVisibleRect().y());
  
    // While the mouse is pressed, timer should trigger more scroll events.
    EXPECT_TRUE(timer.IsRunning());
  
    // Upon release timer should stop (and scroll position should remain).
    scroll_bar->OnMouseEvent(&release);
    EXPECT_FALSE(timer.IsRunning());
    EXPECT_EQ(kDefaultHeight, scroll_view->GetVisibleRect().y());
  }
  
  // Test that LocatedEvents are transformed correctly when scrolling.
  TEST_F(WidgetScrollViewTest, EventLocation) {
    // Set up with both scrollers.
    CustomView* contents = new CustomView;
    contents->SetPreferredSize(gfx::Size(kDefaultHeight * 5, kDefaultHeight * 5));
    AddScrollViewWithContents(contents);
  
    const gfx::Point location_in_widget(10, 10);
  
    // Click without scrolling.
    TestClickAt(location_in_widget);
    EXPECT_EQ(location_in_widget, contents->last_location());
  
    // Scroll down a page.
    contents->ScrollRectToVisible(
        gfx::Rect(0, kDefaultHeight, 1, kDefaultHeight));
    TestClickAt(location_in_widget);
    EXPECT_EQ(gfx::Point(10, 10 + kDefaultHeight), contents->last_location());
  
    // Scroll right a page (and back up).
    contents->ScrollRectToVisible(gfx::Rect(kDefaultWidth, 0, kDefaultWidth, 1));
    TestClickAt(location_in_widget);
    EXPECT_EQ(gfx::Point(10 + kDefaultWidth, 10), contents->last_location());
  
    // Scroll both directions.
    contents->ScrollRectToVisible(
        gfx::Rect(kDefaultWidth, kDefaultHeight, kDefaultWidth, kDefaultHeight));
    TestClickAt(location_in_widget);
    EXPECT_EQ(gfx::Point(10 + kDefaultWidth, 10 + kDefaultHeight),
              contents->last_location());
  }
  
  // Ensure behavior of ScrollRectToVisible() is consistent when scrolling with
  // and without layers, and under LTR and RTL.
  TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetWithoutLayers) {
    // Set up with both scrollers. And a nested view hierarchy like:
    // +-------------+
    // |XX           |
    // |  +----------|
    // |  |          |
    // |  |  +-------|
    // |  |  |       |
    // |  |  |  etc. |
    // |  |  |       |
    // +-------------+
    // Note that "XX" indicates the size of the viewport.
    constexpr int kNesting = 5;
    constexpr int kCellWidth = kDefaultWidth;
    constexpr int kCellHeight = kDefaultHeight;
    constexpr gfx::Size kContentSize(kCellWidth * kNesting,
                                     kCellHeight * kNesting);
    ScrollView* scroll_view = AddScrollViewWithContentSize(kContentSize, false);
    ScrollViewTestApi test_api(scroll_view);
    EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
  
    // Sanity check that the contents has a layer iff testing layers.
    EXPECT_EQ(IsTestingLayers(), !!scroll_view->contents()->layer());
  
    if (IsTestingRtl()) {
      // Sanity check the hit-testing logic on the root view. That is, verify that
      // coordinates really do flip in RTL. The difference inside the viewport is
      // that the flipping should occur consistently in the entire contents (not
      // just the visible contents), and take into account the scroll offset.
      EXPECT_EQ(gfx::Point(kDefaultWidth - 1, 1),
                HitTestInCorner(scroll_view->GetWidget()->GetRootView(), false));
      EXPECT_EQ(gfx::Point(kContentSize.width() - 1, 1),
                HitTestInCorner(scroll_view->contents(), false));
    } else {
      EXPECT_EQ(gfx::Point(1, 1),
                HitTestInCorner(scroll_view->GetWidget()->GetRootView(), false));
      EXPECT_EQ(gfx::Point(1, 1),
                HitTestInCorner(scroll_view->contents(), false));
    }
  
    // Test vertical scrolling using coordinates on the contents canvas.
    gfx::Rect offset(0, kCellHeight * 2, kCellWidth, kCellHeight);
    scroll_view->contents()->ScrollRectToVisible(offset);
    EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
  
    // Rely on auto-flipping for this and future HitTestInCorner() calls.
    EXPECT_EQ(gfx::Point(1, kCellHeight * 2 + 1),
              HitTestInCorner(scroll_view->contents()));
  
    // Test horizontal scrolling.
    offset.set_x(kCellWidth * 2);
    scroll_view->contents()->ScrollRectToVisible(offset);
    EXPECT_EQ(gfx::ScrollOffset(offset.x(), offset.y()),
              test_api.CurrentOffset());
    EXPECT_EQ(gfx::Point(kCellWidth * 2 + 1, kCellHeight * 2 + 1),
              HitTestInCorner(scroll_view->contents()));
  
    // Reset the scrolling.
    scroll_view->contents()->ScrollRectToVisible(gfx::Rect(0, 0, 1, 1));
  
    // Test transformations through a nested view hierarchy.
    View* deepest_view = scroll_view->contents();
    constexpr gfx::Rect kCellRect(kCellWidth, kCellHeight, kContentSize.width(),
                                  kContentSize.height());
    for (int i = 1; i < kNesting; ++i) {
      SCOPED_TRACE(testing::Message("Nesting = ") << i);
      View* child = new View;
      child->SetBoundsRect(kCellRect);
      deepest_view->AddChildView(child);
      deepest_view = child;
  
      // Add a view in one quadrant. Scrolling just this view should only scroll
      // far enough for it to become visible. That is, it should be positioned at
      // the bottom right of the viewport, not the top-left. But since there are
      // scroll bars, the scroll offset needs to go "a bit more".
      View* partial_view = new View;
      partial_view->SetSize(gfx::Size(kCellWidth / 3, kCellHeight / 3));
      deepest_view->AddChildView(partial_view);
      partial_view->ScrollViewToVisible();
      int x_offset_in_cell = kCellWidth - partial_view->width();
      if (!scroll_view->horizontal_scroll_bar()->OverlapsContent())
        x_offset_in_cell -= scroll_view->horizontal_scroll_bar()->GetThickness();
      int y_offset_in_cell = kCellHeight - partial_view->height();
      if (!scroll_view->vertical_scroll_bar()->OverlapsContent())
        y_offset_in_cell -= scroll_view->vertical_scroll_bar()->GetThickness();
      EXPECT_EQ(gfx::ScrollOffset(kCellWidth * i - x_offset_in_cell,
                                  kCellHeight * i - y_offset_in_cell),
                test_api.CurrentOffset());
  
      // Now scroll the rest.
      deepest_view->ScrollViewToVisible();
      EXPECT_EQ(gfx::ScrollOffset(kCellWidth * i, kCellHeight * i),
                test_api.CurrentOffset());
  
      // The partial view should now be at the top-left of the viewport (top-right
      // in RTL).
      EXPECT_EQ(gfx::Point(1, 1), HitTestInCorner(partial_view));
  
      gfx::Point origin;
      View::ConvertPointToWidget(partial_view, &origin);
      constexpr gfx::Point kTestPointRTL(kDefaultWidth - kCellWidth / 3, 0);
      EXPECT_EQ(IsTestingRtl() ? kTestPointRTL : gfx::Point(), origin);
    }
  
    // Scrolling to the deepest view should have moved the viewport so that the
    // (kNesting - 1) parent views are all off-screen.
    EXPECT_EQ(gfx::ScrollOffset(kCellWidth * (kNesting - 1),
                                kCellHeight * (kNesting - 1)),
              test_api.CurrentOffset());
  }
  
  // Test that views scroll offsets are in sync with the layer scroll offsets.
  TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetUsingLayers) {
    // Set up with both scrollers, but don't commit the layer changes yet.
    ScrollView* scroll_view = AddScrollViewWithContentSize(
        gfx::Size(kDefaultWidth * 5, kDefaultHeight * 5), false);
    ScrollViewTestApi test_api(scroll_view);
  
    EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
  
    // UI code may request a scroll before layer changes are committed.
    gfx::Rect offset(0, kDefaultHeight * 2, kDefaultWidth, kDefaultHeight);
    scroll_view->contents()->ScrollRectToVisible(offset);
    EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
  
    // The following only makes sense when layered scrolling is enabled.
    View* container = scroll_view->contents();
    EXPECT_EQ(IsTestingLayers(), !!container->layer());
    if (!container->layer())
      return;
  
    // Container and viewport should have layers.
    EXPECT_TRUE(container->layer());
    EXPECT_TRUE(test_api.contents_viewport()->layer());
  
    // In a Widget, so there should be a compositor.
    ui::Compositor* compositor = container->layer()->GetCompositor();
    EXPECT_TRUE(compositor);
  
    // But setting on the impl side should fail since the layer isn't committed.
    int layer_id = container->layer()->cc_layer_for_testing()->id();
    EXPECT_FALSE(compositor->ScrollLayerTo(layer_id, gfx::ScrollOffset(0, 0)));
    EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
  
    WaitForCommit();
    EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
  
    // Upon commit, the impl side should report the same value too.
    gfx::ScrollOffset impl_offset;
    EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset));
    EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), impl_offset);
  
    // Now impl-side scrolling should work, and also update the ScrollView.
    offset.set_y(kDefaultHeight * 3);
    EXPECT_TRUE(
        compositor->ScrollLayerTo(layer_id, gfx::ScrollOffset(0, offset.y())));
    EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
  
    // Scroll via ScrollView API. Should be reflected on the impl side.
    offset.set_y(kDefaultHeight * 4);
    scroll_view->contents()->ScrollRectToVisible(offset);
    EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
  
    EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset));
    EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), impl_offset);
  
    // Test horizontal scrolling.
    offset.set_x(kDefaultWidth * 2);
    scroll_view->contents()->ScrollRectToVisible(offset);
    EXPECT_EQ(gfx::ScrollOffset(offset.x(), offset.y()),
              test_api.CurrentOffset());
  
    EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset));
    EXPECT_EQ(gfx::ScrollOffset(offset.x(), offset.y()), impl_offset);
  }
  
  // Tests to see the scroll events are handled correctly in composited and
  // non-composited scrolling.
  TEST_F(WidgetScrollViewTest, CompositedScrollEvents) {
    // Set up with a vertical scroll bar.
    ScrollView* scroll_view =
        AddScrollViewWithContentSize(gfx::Size(10, kDefaultHeight * 5));
    ScrollViewTestApi test_api(scroll_view);
  
    // Create a fake scroll event and send it to the scroll view.
    ui::ScrollEvent scroll(ui::ET_SCROLL, gfx::Point(), base::TimeTicks::Now(), 0,
                           0, -10, 0, -10, 3);
    EXPECT_FALSE(scroll.handled());
    EXPECT_FALSE(scroll.stopped_propagation());
    scroll_view->OnScrollEvent(&scroll);
  
    // Check to see if the scroll event is handled by the scroll view.
    if (base::FeatureList::IsEnabled(features::kUiCompositorScrollWithLayers)) {
      // If UiCompositorScrollWithLayers is enabled, the event is set handled
      // and its propagation is stopped.
      EXPECT_TRUE(scroll.handled());
      EXPECT_TRUE(scroll.stopped_propagation());
    } else {
      // If UiCompositorScrollWithLayers is disabled, the event isn't handled.
      // This informs Widget::OnScrollEvent() to convert to a MouseWheel event
      // and dispatch again. Simulate that.
      EXPECT_FALSE(scroll.handled());
      EXPECT_FALSE(scroll.stopped_propagation());
      EXPECT_EQ(gfx::ScrollOffset(), test_api.CurrentOffset());
  
      ui::MouseWheelEvent wheel(scroll);
      scroll_view->OnMouseEvent(&wheel);
    }
  
    // Check if the scroll view has been offset.
    EXPECT_EQ(gfx::ScrollOffset(0, 10), test_api.CurrentOffset());
  }
  
  INSTANTIATE_TEST_CASE_P(,
                          WidgetScrollViewTestRTLAndLayers,
                          ::testing::Values(UiConfig::kLtr,
                                            UiConfig::kRtl,
                                            UiConfig::kLtrWithLayers,
                                            UiConfig::kRtlWithLayers),
                          &UiConfigToString);
  
  }  // namespace views
  