#include "ui/views/controls/scroll_view.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/gtest_util.h"
#include "base/test/icu_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_type.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/views/accessibility/view_accessibility.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/test/test_views.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view_observer.h"
#include "ui/views/view_test_api.h"
#include "ui/views/view_tracker.h"
#if BUILDFLAG(IS_MAC)
#include "ui/base/test/scoped_preferred_scroller_style_mac.h"
#endif
enum ScrollBarOrientation { … };
namespace views {
namespace test {
class ScrollViewTestApi { … };
}
namespace {
const int kWidth = …;
const int kMinHeight = …;
const int kMaxHeight = …;
class FixedView : public View { … };
BEGIN_METADATA(…)
class CustomView : public View { … };
BEGIN_METADATA(…)
void CheckScrollbarVisibility(const ScrollView* scroll_view,
ScrollBarOrientation orientation,
bool should_be_visible) { … }
ui::MouseEvent TestLeftMouseAt(const gfx::Point& location, ui::EventType type) { … }
class VerticalResizingView : public View { … };
BEGIN_METADATA(…)
class HorizontalResizingView : public View { … };
BEGIN_METADATA(…)
class TestScrollBarThumb : public BaseScrollBarThumb { … };
BEGIN_METADATA(…)
class TestScrollBar : public ScrollBar { … };
BEGIN_METADATA(…)
}
ScrollViewTestApi;
class ScrollViewTest : public ViewsTestBase { … };
class WidgetScrollViewTest : public test::WidgetTest,
public ui::CompositorObserver { … };
constexpr int WidgetScrollViewTest::kDefaultHeight;
constexpr int WidgetScrollViewTest::kDefaultWidth;
enum class UiConfig { … };
class WidgetScrollViewTestRTLAndLayers
: public WidgetScrollViewTest,
public ::testing::WithParamInterface<UiConfig> { … };
std::string UiConfigToString(const testing::TestParamInfo<UiConfig>& info) { … }
TEST_F(ScrollViewTest, ViewportSizedToFit) { … }
TEST_F(ScrollViewTest, BoundedViewportSizedToFit) { … }
TEST_F(ScrollViewTest, VerticalScrollbarDoesNotAppearUnnecessarily) { … }
TEST_F(ScrollViewTest, HorizontalScrollbarDoesNotAppearIfHidden) { … }
TEST_F(ScrollViewTest, VerticalScrollbarDoesNotAppearIfHidden) { … }
TEST_F(ScrollViewTest, HorizontalScrollbarDoesNotAppearIfDisabled) { … }
TEST_F(ScrollViewTest, VerticallScrollbarDoesNotAppearIfDisabled) { … }
TEST_F(ScrollViewTest, AccessibleProperties) { … }
TEST_F(ScrollViewTest, ScrollBars) { … }
TEST_F(WidgetScrollViewTest, ScrollChildToVisibleOnFocusWithTooltip) { … }
TEST_F(WidgetScrollViewTest, Header) { … }
TEST_F(ScrollViewTest, ScrollBarsWithHeader) { … }
TEST_F(ScrollViewTest, HeaderScrollsWithContent) { … }
TEST_F(ScrollViewTest, ScrollToPositionUpdatesScrollBar) { … }
TEST_F(ScrollViewTest, HorizontalScrollBarAccessibleScrollXProperties) { … }
TEST_F(ScrollViewTest, VerticalScrollBarAccessibleScrollYProperties) { … }
TEST_F(ScrollViewTest, ScrollToPositionUpdatesWithHiddenHorizontalScrollBar) { … }
TEST_F(ScrollViewTest, ScrollToPositionUpdatesWithHiddenVerticalScrollBar) { … }
TEST_F(ScrollViewTest, ScrollRectToVisible) { … }
TEST_F(ScrollViewTest, ScrollByOffset) { … }
TEST_F(ScrollViewTest, ScrollRectToVisibleWithHiddenHorizontalScrollbar) { … }
TEST_F(ScrollViewTest, ScrollRectToVisibleWithHiddenVerticalScrollbar) { … }
TEST_F(ScrollViewTest, ScrollRectToVisibleWithDisabledScrollbars) { … }
TEST_F(ScrollViewTest, ScrollChildToVisibleOnFocus) { … }
TEST_F(ScrollViewTest, ScrollViewToVisibleOnContentsRootFocus) { … }
TEST_F(ScrollViewTest, ClipHeightToNormalContentHeight) { … }
TEST_F(ScrollViewTest, ClipHeightToShortContentHeight) { … }
TEST_F(ScrollViewTest, ClipHeightToTallContentHeight) { … }
TEST_F(ScrollViewTest, ClipHeightToScrollbarUsesWidth) { … }
TEST_F(ScrollViewTest, ClipHeightToUpdatesPreferredSize) { … }
TEST_F(ScrollViewTest, CornerViewVisibility) { … }
TEST_F(WidgetScrollViewTest, ChildWithLayerTest) { … }
TEST_F(ScrollViewTest, DontCreateLayerOnViewportIfLayerOnScrollViewCreated) { … }
TEST_F(ScrollViewTest, ContentsViewportLayerUsed_ScrollWithLayersDisabled) { … }
TEST_F(
ScrollViewTest,
ContentsViewportLayerWhenContentsDoesNotHaveLayer_ScrollWithLayersDisabled) { … }
TEST_F(
ScrollViewTest,
ContentsLayerCannotBeChangedAfterContentsAreSet_ScrollWithLayersEnabled) { … }
TEST_F(ScrollViewTest,
ContentsLayerCanBeChangedAfterContentsAreSet_ScrollWithLayersDisabled) { … }
TEST_F(
ScrollViewTest,
ContentsViewportLayerUsedWhenScrollViewContentsAreChanged_ScrollWithLayersDisabled) { … }
TEST_F(ScrollViewTest, ContentsViewportLayerUsed_ScrollWithLayersEnabled) { … }
TEST_F(
ScrollViewTest,
ContentsViewportLayerUsedWhenNotDrawnUsedForContents_ScrollWithLayersEnabled) { … }
TEST_F(ScrollViewTest,
ContentsViewportLayerHasRoundedCorners_ScrollWithLayersEnabled) { … }
#if BUILDFLAG(IS_MAC)
TEST_F(ScrollViewTest, CocoaOverlayScrollBars) {
SetOverlayScrollersEnabled(true);
View* contents = InstallContents();
contents->SetBounds(0, 0, 50, 400);
InvalidateAndRunScheduledLayoutOnScrollView();
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);
contents->SetBounds(0, 0, 400, 50);
InvalidateAndRunScheduledLayoutOnScrollView();
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);
contents->SetBounds(0, 0, 300, 400);
InvalidateAndRunScheduledLayoutOnScrollView();
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);
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());
SetOverlayScrollersEnabled(false);
EXPECT_TRUE(ViewTestApi(scroll_view_.get()).needs_layout());
views::test::RunScheduledLayout(scroll_view_.get());
EXPECT_EQ(100 - VerticalScrollBarWidth(), contents->parent()->width());
EXPECT_EQ(100 - HorizontalScrollBarHeight(), contents->parent()->height());
EXPECT_NE(0, VerticalScrollBarWidth());
EXPECT_NE(0, HorizontalScrollBarHeight());
}
TEST_F(WidgetScrollViewTest,
OverlayScrollBarsCannotProcessEventsWhenTransparent) {
ui::ScopedAnimationDurationScaleMode really_animate(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SetUseOverlayScrollers();
ScrollView* scroll_view = AddScrollViewWithContentSize(
gfx::Size(kDefaultWidth * 5, kDefaultHeight * 5));
ScrollViewTestApi test_api(scroll_view);
ScrollBar* scroll_bar = test_api.GetScrollBar(HORIZONTAL);
EXPECT_FALSE(scroll_bar->GetCanProcessEventsWithinSubtree());
ui::test::EventGenerator generator(
GetContext(), scroll_view->GetWidget()->GetNativeWindow());
generator.GenerateTrackpadRest();
EXPECT_TRUE(scroll_bar->GetCanProcessEventsWithinSubtree());
}
TEST_F(WidgetScrollViewTest, ScrollersOnRest) {
ui::ScopedAnimationDurationScaleMode really_animate(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
const float kMaxOpacity = 0.8f;
SetUseOverlayScrollers();
ScrollView* scroll_view = AddScrollViewWithContentSize(
gfx::Size(kDefaultWidth * 5, kDefaultHeight * 5));
ScrollViewTestApi test_api(scroll_view);
const auto bar = std::to_array<ScrollBar*>(
{test_api.GetScrollBar(HORIZONTAL), test_api.GetScrollBar(VERTICAL)});
const auto hide_timer = std::to_array<base::RetainingOneShotTimer*>(
{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();
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();
for (ScrollBarOrientation orientation : {HORIZONTAL, VERTICAL}) {
EXPECT_EQ(kMaxOpacity, bar[orientation]->layer()->GetTargetOpacity());
EXPECT_TRUE(hide_timer[orientation]->IsRunning());
hide_timer[orientation]->user_task().Run();
hide_timer[orientation]->Stop();
EXPECT_EQ(0, bar[orientation]->layer()->GetTargetOpacity());
}
generator.GenerateTrackpadRest();
EXPECT_EQ(kMaxOpacity, bar[HORIZONTAL]->layer()->GetTargetOpacity());
EXPECT_EQ(kMaxOpacity, bar[VERTICAL]->layer()->GetTargetOpacity());
const float y_offset = 3;
const int kSteps = 1;
const int kNnumFingers = 2;
generator.ScrollSequence(generator.current_screen_location(),
base::TimeDelta(), 0, y_offset, kSteps,
kNnumFingers);
EXPECT_EQ(kMaxOpacity, bar[HORIZONTAL]->layer()->opacity());
EXPECT_EQ(0, bar[HORIZONTAL]->layer()->GetTargetOpacity());
EXPECT_FALSE(hide_timer[HORIZONTAL]->IsRunning());
EXPECT_EQ(kMaxOpacity, bar[VERTICAL]->layer()->opacity());
EXPECT_EQ(kMaxOpacity, bar[VERTICAL]->layer()->GetTargetOpacity());
EXPECT_TRUE(hide_timer[VERTICAL]->IsRunning());
EXPECT_EQ(gfx::PointF(0, y_offset), test_api.CurrentOffset());
const float x_offset = 5;
generator.ScrollSequence(generator.current_screen_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());
}
EXPECT_EQ(gfx::PointF(x_offset, y_offset), test_api.CurrentOffset());
}
#endif
TEST_F(ScrollViewTest, ConstrainScrollToBounds) { … }
TEST_F(ScrollViewTest, ContentScrollNotResetOnLayout) { … }
TEST_F(ScrollViewTest, ArrowKeyScrolling) { … }
TEST_F(ScrollViewTest, ArrowKeyScrollingDisabled) { … }
TEST_F(ScrollViewTest, VerticalOverflowIndicators) { … }
TEST_F(ScrollViewTest, HorizontalOverflowIndicators) { … }
TEST_F(ScrollViewTest, HorizontalVerticalOverflowIndicators) { … }
TEST_F(ScrollViewTest, VerticalWithHeaderOverflowIndicators) { … }
TEST_F(ScrollViewTest, CustomOverflowIndicator) { … }
TEST_F(ScrollViewTest, IgnoreOverlapWithDisabledHorizontalScroll) { … }
TEST_F(ScrollViewTest, IgnoreOverlapWithHiddenHorizontalScroll) { … }
TEST_F(ScrollViewTest, IgnoreOverlapWithDisabledVerticalScroll) { … }
TEST_F(ScrollViewTest, IgnoreOverlapWithHiddenVerticalScroll) { … }
TEST_F(ScrollViewTest, TestSettingContentsToNull) { … }
TEST_F(WidgetScrollViewTest, ScrollTrackScrolling) { … }
TEST_F(WidgetScrollViewTest, EventLocation) { … }
TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetWithoutLayers) { … }
TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetUsingLayers) { … }
namespace {
static void ApplyScrollEvent(const ScrollViewTestApi& test_api,
ScrollView* scroll_view,
ui::ScrollEvent& scroll_event) { … }
}
TEST_F(WidgetScrollViewTest, CompositedScrollEvents) { … }
TEST_F(WidgetScrollViewTest, CompositedTransposedScrollEvents) { … }
TEST_F(WidgetScrollViewTest,
DISABLED_CompositedTransposedScrollEventsHorizontalComponentIsLarger) { … }
TEST_F(WidgetScrollViewTest,
CompositedTransposedScrollEventsVerticalComponentIsLarger) { … }
TEST_F(WidgetScrollViewTest, UnboundedScrollViewUsesContentPreferredSize) { … }
INSTANTIATE_TEST_SUITE_P(…);
}