blob: c6340c0c69543d128c81b5c1097abe9fd0702f98 [file] [log] [blame]
// Copyright 2015 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 "third_party/blink/renderer/platform/scroll/scrollable_area.h"
#include "base/message_loop/message_loop.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/graphics/color.h"
#include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
#include "third_party/blink/renderer/platform/scroll/scrollbar_test_suite.h"
#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.h"
#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay_mock.h"
#include "third_party/blink/renderer/platform/testing/fake_graphics_layer.h"
#include "third_party/blink/renderer/platform/testing/fake_graphics_layer_client.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
namespace blink {
namespace {
using testing::_;
using testing::Return;
class ScrollbarThemeWithMockInvalidation : public ScrollbarThemeMock {
public:
MOCK_CONST_METHOD0(ShouldRepaintAllPartsOnInvalidation, bool());
MOCK_CONST_METHOD3(InvalidateOnThumbPositionChange,
ScrollbarPart(const Scrollbar&, float, float));
};
} // namespace
class ScrollableAreaTest : public testing::Test {
private:
base::MessageLoop message_loop_;
};
TEST_F(ScrollableAreaTest, ScrollAnimatorCurrentPositionShouldBeSync) {
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform;
MockScrollableArea* scrollable_area =
MockScrollableArea::Create(ScrollOffset(0, 100));
scrollable_area->SetScrollOffset(ScrollOffset(0, 10000), kCompositorScroll);
EXPECT_EQ(100.0,
scrollable_area->GetScrollAnimator().CurrentOffset().Height());
}
TEST_F(ScrollableAreaTest, ScrollbarTrackAndThumbRepaint) {
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform;
ScrollbarThemeWithMockInvalidation theme;
MockScrollableArea* scrollable_area =
MockScrollableArea::Create(ScrollOffset(0, 100));
Scrollbar* scrollbar = Scrollbar::CreateForTesting(
scrollable_area, kHorizontalScrollbar, kRegularScrollbar, &theme);
EXPECT_CALL(theme, ShouldRepaintAllPartsOnInvalidation())
.WillRepeatedly(Return(true));
EXPECT_TRUE(scrollbar->TrackNeedsRepaint());
EXPECT_TRUE(scrollbar->ThumbNeedsRepaint());
scrollbar->SetNeedsPaintInvalidation(kNoPart);
EXPECT_TRUE(scrollbar->TrackNeedsRepaint());
EXPECT_TRUE(scrollbar->ThumbNeedsRepaint());
scrollbar->ClearTrackNeedsRepaint();
scrollbar->ClearThumbNeedsRepaint();
EXPECT_FALSE(scrollbar->TrackNeedsRepaint());
EXPECT_FALSE(scrollbar->ThumbNeedsRepaint());
scrollbar->SetNeedsPaintInvalidation(kThumbPart);
EXPECT_TRUE(scrollbar->TrackNeedsRepaint());
EXPECT_TRUE(scrollbar->ThumbNeedsRepaint());
// When not all parts are repainted on invalidation,
// setNeedsPaintInvalidation sets repaint bits only on the requested parts.
EXPECT_CALL(theme, ShouldRepaintAllPartsOnInvalidation())
.WillRepeatedly(Return(false));
scrollbar->ClearTrackNeedsRepaint();
scrollbar->ClearThumbNeedsRepaint();
EXPECT_FALSE(scrollbar->TrackNeedsRepaint());
EXPECT_FALSE(scrollbar->ThumbNeedsRepaint());
scrollbar->SetNeedsPaintInvalidation(kThumbPart);
EXPECT_FALSE(scrollbar->TrackNeedsRepaint());
EXPECT_TRUE(scrollbar->ThumbNeedsRepaint());
// Forced GC in order to finalize objects depending on the mock object.
ThreadState::Current()->CollectAllGarbage();
}
TEST_F(ScrollableAreaTest, ScrollbarGraphicsLayerInvalidation) {
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform;
ScrollbarTheme::SetMockScrollbarsEnabled(true);
MockScrollableArea* scrollable_area =
MockScrollableArea::Create(ScrollOffset(0, 100));
FakeGraphicsLayerClient graphics_layer_client;
graphics_layer_client.SetIsTrackingRasterInvalidations(true);
FakeGraphicsLayer graphics_layer(graphics_layer_client);
graphics_layer.SetDrawsContent(true);
graphics_layer.SetSize(IntSize(111, 222));
EXPECT_CALL(*scrollable_area, LayerForHorizontalScrollbar())
.WillRepeatedly(Return(&graphics_layer));
Scrollbar* scrollbar = Scrollbar::Create(
scrollable_area, kHorizontalScrollbar, kRegularScrollbar, nullptr);
graphics_layer.ResetTrackedRasterInvalidations();
scrollbar->SetNeedsPaintInvalidation(kNoPart);
EXPECT_TRUE(graphics_layer.HasTrackedRasterInvalidations());
// Forced GC in order to finalize objects depending on the mock object.
ThreadState::Current()->CollectAllGarbage();
}
TEST_F(ScrollableAreaTest, InvalidatesNonCompositedScrollbarsWhenThumbMoves) {
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform;
ScrollbarThemeWithMockInvalidation theme;
MockScrollableArea* scrollable_area =
MockScrollableArea::Create(ScrollOffset(100, 100));
Scrollbar* horizontal_scrollbar = Scrollbar::CreateForTesting(
scrollable_area, kHorizontalScrollbar, kRegularScrollbar, &theme);
Scrollbar* vertical_scrollbar = Scrollbar::CreateForTesting(
scrollable_area, kVerticalScrollbar, kRegularScrollbar, &theme);
EXPECT_CALL(*scrollable_area, HorizontalScrollbar())
.WillRepeatedly(Return(horizontal_scrollbar));
EXPECT_CALL(*scrollable_area, VerticalScrollbar())
.WillRepeatedly(Return(vertical_scrollbar));
// Regardless of whether the theme invalidates any parts, non-composited
// scrollbars have to be repainted if the thumb moves.
EXPECT_CALL(*scrollable_area, LayerForHorizontalScrollbar())
.WillRepeatedly(Return(nullptr));
EXPECT_CALL(*scrollable_area, LayerForVerticalScrollbar())
.WillRepeatedly(Return(nullptr));
ASSERT_FALSE(scrollable_area->HasLayerForVerticalScrollbar());
ASSERT_FALSE(scrollable_area->HasLayerForHorizontalScrollbar());
EXPECT_CALL(theme, ShouldRepaintAllPartsOnInvalidation())
.WillRepeatedly(Return(false));
EXPECT_CALL(theme, InvalidateOnThumbPositionChange(_, _, _))
.WillRepeatedly(Return(kNoPart));
// A scroll in each direction should only invalidate one scrollbar.
scrollable_area->SetScrollOffset(ScrollOffset(0, 50), kProgrammaticScroll);
EXPECT_FALSE(scrollable_area->HorizontalScrollbarNeedsPaintInvalidation());
EXPECT_TRUE(scrollable_area->VerticalScrollbarNeedsPaintInvalidation());
scrollable_area->ClearNeedsPaintInvalidationForScrollControls();
scrollable_area->SetScrollOffset(ScrollOffset(50, 50), kProgrammaticScroll);
EXPECT_TRUE(scrollable_area->HorizontalScrollbarNeedsPaintInvalidation());
EXPECT_FALSE(scrollable_area->VerticalScrollbarNeedsPaintInvalidation());
scrollable_area->ClearNeedsPaintInvalidationForScrollControls();
// Forced GC in order to finalize objects depending on the mock object.
ThreadState::Current()->CollectAllGarbage();
}
TEST_F(ScrollableAreaTest, InvalidatesCompositedScrollbarsIfPartsNeedRepaint) {
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform;
ScrollbarThemeWithMockInvalidation theme;
MockScrollableArea* scrollable_area =
MockScrollableArea::Create(ScrollOffset(100, 100));
Scrollbar* horizontal_scrollbar = Scrollbar::CreateForTesting(
scrollable_area, kHorizontalScrollbar, kRegularScrollbar, &theme);
horizontal_scrollbar->ClearTrackNeedsRepaint();
horizontal_scrollbar->ClearThumbNeedsRepaint();
Scrollbar* vertical_scrollbar = Scrollbar::CreateForTesting(
scrollable_area, kVerticalScrollbar, kRegularScrollbar, &theme);
vertical_scrollbar->ClearTrackNeedsRepaint();
vertical_scrollbar->ClearThumbNeedsRepaint();
EXPECT_CALL(*scrollable_area, HorizontalScrollbar())
.WillRepeatedly(Return(horizontal_scrollbar));
EXPECT_CALL(*scrollable_area, VerticalScrollbar())
.WillRepeatedly(Return(vertical_scrollbar));
// Composited scrollbars only need repainting when parts become invalid
// (e.g. if the track changes appearance when the thumb reaches the end).
FakeGraphicsLayerClient graphics_layer_client;
graphics_layer_client.SetIsTrackingRasterInvalidations(true);
FakeGraphicsLayer layer_for_horizontal_scrollbar(graphics_layer_client);
layer_for_horizontal_scrollbar.SetDrawsContent(true);
layer_for_horizontal_scrollbar.SetSize(IntSize(10, 10));
FakeGraphicsLayer layer_for_vertical_scrollbar(graphics_layer_client);
layer_for_vertical_scrollbar.SetDrawsContent(true);
layer_for_vertical_scrollbar.SetSize(IntSize(10, 10));
EXPECT_CALL(*scrollable_area, LayerForHorizontalScrollbar())
.WillRepeatedly(Return(&layer_for_horizontal_scrollbar));
EXPECT_CALL(*scrollable_area, LayerForVerticalScrollbar())
.WillRepeatedly(Return(&layer_for_vertical_scrollbar));
ASSERT_TRUE(scrollable_area->HasLayerForHorizontalScrollbar());
ASSERT_TRUE(scrollable_area->HasLayerForVerticalScrollbar());
EXPECT_CALL(theme, ShouldRepaintAllPartsOnInvalidation())
.WillRepeatedly(Return(false));
// First, we'll scroll horizontally, and the theme will require repainting
// the back button (i.e. the track).
EXPECT_CALL(theme, InvalidateOnThumbPositionChange(_, _, _))
.WillOnce(Return(kBackButtonStartPart));
scrollable_area->SetScrollOffset(ScrollOffset(50, 0), kProgrammaticScroll);
EXPECT_TRUE(layer_for_horizontal_scrollbar.HasTrackedRasterInvalidations());
EXPECT_FALSE(layer_for_vertical_scrollbar.HasTrackedRasterInvalidations());
EXPECT_TRUE(horizontal_scrollbar->TrackNeedsRepaint());
EXPECT_FALSE(horizontal_scrollbar->ThumbNeedsRepaint());
layer_for_horizontal_scrollbar.ResetTrackedRasterInvalidations();
horizontal_scrollbar->ClearTrackNeedsRepaint();
// Next, we'll scroll vertically, but invalidate the thumb.
EXPECT_CALL(theme, InvalidateOnThumbPositionChange(_, _, _))
.WillOnce(Return(kThumbPart));
scrollable_area->SetScrollOffset(ScrollOffset(50, 50), kProgrammaticScroll);
EXPECT_FALSE(layer_for_horizontal_scrollbar.HasTrackedRasterInvalidations());
EXPECT_TRUE(layer_for_vertical_scrollbar.HasTrackedRasterInvalidations());
EXPECT_FALSE(vertical_scrollbar->TrackNeedsRepaint());
EXPECT_TRUE(vertical_scrollbar->ThumbNeedsRepaint());
layer_for_vertical_scrollbar.ResetTrackedRasterInvalidations();
vertical_scrollbar->ClearThumbNeedsRepaint();
// Next we'll scroll in both, but the thumb position moving requires no
// invalidations. Nonetheless the GraphicsLayer should be invalidated,
// because we still need to update the underlying layer (though no
// rasterization will be required).
EXPECT_CALL(theme, InvalidateOnThumbPositionChange(_, _, _))
.Times(2)
.WillRepeatedly(Return(kNoPart));
scrollable_area->SetScrollOffset(ScrollOffset(70, 70), kProgrammaticScroll);
EXPECT_TRUE(layer_for_horizontal_scrollbar.HasTrackedRasterInvalidations());
EXPECT_TRUE(layer_for_vertical_scrollbar.HasTrackedRasterInvalidations());
EXPECT_FALSE(horizontal_scrollbar->TrackNeedsRepaint());
EXPECT_FALSE(horizontal_scrollbar->ThumbNeedsRepaint());
EXPECT_FALSE(vertical_scrollbar->TrackNeedsRepaint());
EXPECT_FALSE(vertical_scrollbar->ThumbNeedsRepaint());
// Forced GC in order to finalize objects depending on the mock object.
ThreadState::Current()->CollectAllGarbage();
}
TEST_F(ScrollableAreaTest, RecalculatesScrollbarOverlayIfBackgroundChanges) {
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform;
MockScrollableArea* scrollable_area =
MockScrollableArea::Create(ScrollOffset(0, 100));
EXPECT_EQ(kScrollbarOverlayColorThemeDark,
scrollable_area->GetScrollbarOverlayColorTheme());
scrollable_area->RecalculateScrollbarOverlayColorTheme(Color(34, 85, 51));
EXPECT_EQ(kScrollbarOverlayColorThemeLight,
scrollable_area->GetScrollbarOverlayColorTheme());
scrollable_area->RecalculateScrollbarOverlayColorTheme(Color(236, 143, 185));
EXPECT_EQ(kScrollbarOverlayColorThemeDark,
scrollable_area->GetScrollbarOverlayColorTheme());
}
TEST_F(ScrollableAreaTest, ScrollableAreaDidScroll) {
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform;
MockScrollableArea* scrollable_area =
MockScrollableArea::Create(ScrollOffset(100, 100));
scrollable_area->SetScrollOrigin(IntPoint(20, 30));
scrollable_area->DidScroll(gfx::ScrollOffset(40, 51));
// After calling didScroll, the new offset should account for scroll origin.
EXPECT_EQ(20, scrollable_area->ScrollOffsetInt().Width());
EXPECT_EQ(21, scrollable_area->ScrollOffsetInt().Height());
}
// Scrollbars in popups shouldn't fade out since they aren't composited and thus
// they don't appear on hover so users without a wheel can't scroll if they fade
// out.
TEST_F(ScrollableAreaTest, PopupOverlayScrollbarShouldNotFadeOut) {
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform;
ScopedOverlayScrollbarsForTest overlay_scrollbars(true);
ScrollbarTheme::SetMockScrollbarsEnabled(true);
MockScrollableArea* scrollable_area =
MockScrollableArea::Create(ScrollOffset(0, 100));
scrollable_area->SetIsPopup();
ScrollbarThemeOverlayMock& theme =
(ScrollbarThemeOverlayMock&)scrollable_area->GetPageScrollbarTheme();
theme.SetOverlayScrollbarFadeOutDelay(1);
Scrollbar* scrollbar = Scrollbar::CreateForTesting(
scrollable_area, kHorizontalScrollbar, kRegularScrollbar, &theme);
DCHECK(scrollbar->IsOverlayScrollbar());
DCHECK(scrollbar->Enabled());
scrollable_area->ShowOverlayScrollbars();
// No fade out animation should be posted.
EXPECT_FALSE(scrollable_area->fade_overlay_scrollbars_timer_);
// Forced GC in order to finalize objects depending on the mock object.
ThreadState::Current()->CollectAllGarbage();
}
} // namespace blink