blob: 5f9ac36e693fcd9b551b532a9ef67ec3656d101f [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 "platform/scroll/ScrollableArea.h"
#include "platform/graphics/Color.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/scroll/ScrollbarTheme.h"
#include "platform/scroll/ScrollbarThemeMock.h"
#include "platform/testing/FakeGraphicsLayer.h"
#include "platform/testing/TestingPlatformSupport.h"
#include "public/platform/Platform.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
using testing::_;
using testing::Return;
class MockScrollableArea : public GarbageCollectedFinalized<MockScrollableArea>, public ScrollableArea {
USING_GARBAGE_COLLECTED_MIXIN(MockScrollableArea);
public:
static MockScrollableArea* create(const IntPoint& maximumScrollPosition)
{
return new MockScrollableArea(maximumScrollPosition);
}
MOCK_CONST_METHOD0(visualRectForScrollbarParts, LayoutRect());
MOCK_CONST_METHOD0(isActive, bool());
MOCK_CONST_METHOD1(scrollSize, int(ScrollbarOrientation));
MOCK_CONST_METHOD0(isScrollCornerVisible, bool());
MOCK_CONST_METHOD0(scrollCornerRect, IntRect());
MOCK_CONST_METHOD0(horizontalScrollbar, Scrollbar*());
MOCK_CONST_METHOD0(verticalScrollbar, Scrollbar*());
MOCK_METHOD0(scrollControlWasSetNeedsPaintInvalidation, void());
MOCK_CONST_METHOD0(enclosingScrollableArea, ScrollableArea*());
MOCK_CONST_METHOD1(visibleContentRect, IntRect(IncludeScrollbarsInRect));
MOCK_CONST_METHOD0(contentsSize, IntSize());
MOCK_CONST_METHOD0(scrollableAreaBoundingBox, IntRect());
MOCK_CONST_METHOD0(layerForHorizontalScrollbar, GraphicsLayer*());
MOCK_CONST_METHOD0(layerForVerticalScrollbar, GraphicsLayer*());
bool userInputScrollable(ScrollbarOrientation) const override { return true; }
bool scrollbarsCanBeActive () const override { return true; }
bool shouldPlaceVerticalScrollbarOnLeft() const override { return false; }
void setScrollOffset(const DoublePoint& offset, ScrollType) override { m_scrollPosition = flooredIntPoint(offset).shrunkTo(m_maximumScrollPosition); }
IntPoint scrollPosition() const override { return m_scrollPosition; }
IntPoint minimumScrollPosition() const override { return IntPoint(); }
IntPoint maximumScrollPosition() const override { return m_maximumScrollPosition; }
int visibleHeight() const override { return 768; }
int visibleWidth() const override { return 1024; }
bool scrollAnimatorEnabled() const override { return false; }
int pageStep(ScrollbarOrientation) const override { return 0; }
using ScrollableArea::horizontalScrollbarNeedsPaintInvalidation;
using ScrollableArea::verticalScrollbarNeedsPaintInvalidation;
using ScrollableArea::clearNeedsPaintInvalidationForScrollControls;
DEFINE_INLINE_VIRTUAL_TRACE()
{
ScrollableArea::trace(visitor);
}
private:
explicit MockScrollableArea(const IntPoint& maximumScrollPosition)
: m_maximumScrollPosition(maximumScrollPosition) { }
IntPoint m_scrollPosition;
IntPoint m_maximumScrollPosition;
};
} // namespace
class ScrollableAreaTest : public testing::Test {
public:
ScrollableAreaTest() { }
void SetUp() override
{
TestingPlatformSupport::Config config;
config.compositorSupport = Platform::current()->compositorSupport();
m_fakePlatform = adoptPtr(new TestingPlatformSupportWithMockScheduler(config));
}
void TearDown() override
{
m_fakePlatform = nullptr;
}
private:
OwnPtr<TestingPlatformSupportWithMockScheduler> m_fakePlatform;
};
TEST_F(ScrollableAreaTest, ScrollAnimatorCurrentPositionShouldBeSync)
{
MockScrollableArea* scrollableArea = MockScrollableArea::create(IntPoint(0, 100));
scrollableArea->setScrollPosition(IntPoint(0, 10000), CompositorScroll);
EXPECT_EQ(100.0, scrollableArea->scrollAnimator().currentPosition().y());
}
namespace {
class ScrollbarThemeWithMockInvalidation : public ScrollbarThemeMock {
public:
MOCK_CONST_METHOD0(shouldRepaintAllPartsOnInvalidation, bool());
MOCK_CONST_METHOD3(invalidateOnThumbPositionChange, ScrollbarPart(const ScrollbarThemeClient&, float, float));
};
} // namespace
TEST_F(ScrollableAreaTest, ScrollbarTrackAndThumbRepaint)
{
ScrollbarThemeWithMockInvalidation theme;
MockScrollableArea* scrollableArea = MockScrollableArea::create(IntPoint(0, 100));
Scrollbar* scrollbar = Scrollbar::createForTesting(scrollableArea, HorizontalScrollbar, RegularScrollbar, &theme);
EXPECT_CALL(theme, shouldRepaintAllPartsOnInvalidation()).WillRepeatedly(Return(true));
EXPECT_TRUE(scrollbar->trackNeedsRepaint());
EXPECT_TRUE(scrollbar->thumbNeedsRepaint());
scrollbar->setNeedsPaintInvalidation(NoPart);
EXPECT_TRUE(scrollbar->trackNeedsRepaint());
EXPECT_TRUE(scrollbar->thumbNeedsRepaint());
scrollbar->clearTrackNeedsRepaint();
scrollbar->clearThumbNeedsRepaint();
EXPECT_FALSE(scrollbar->trackNeedsRepaint());
EXPECT_FALSE(scrollbar->thumbNeedsRepaint());
scrollbar->setNeedsPaintInvalidation(ThumbPart);
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(ThumbPart);
EXPECT_FALSE(scrollbar->trackNeedsRepaint());
EXPECT_TRUE(scrollbar->thumbNeedsRepaint());
// Forced GC in order to finalize objects depending on the mock object.
Heap::collectAllGarbage();
}
class MockGraphicsLayerClient : public GraphicsLayerClient {
public:
bool needsRepaint(const GraphicsLayer&) const { return true; }
IntRect computeInterestRect(const GraphicsLayer*, const IntRect&) const { return IntRect(); }
void paintContents(const GraphicsLayer*, GraphicsContext&, GraphicsLayerPaintingPhase, const IntRect&) const override { }
String debugName(const GraphicsLayer*) const override { return String(); }
bool isTrackingPaintInvalidations() const override { return true; }
};
TEST_F(ScrollableAreaTest, ScrollbarGraphicsLayerInvalidation)
{
ScrollbarTheme::setMockScrollbarsEnabled(true);
MockScrollableArea* scrollableArea = MockScrollableArea::create(IntPoint(0, 100));
MockGraphicsLayerClient graphicsLayerClient;
FakeGraphicsLayer graphicsLayer(&graphicsLayerClient);
graphicsLayer.setDrawsContent(true);
graphicsLayer.setSize(FloatSize(111, 222));
EXPECT_CALL(*scrollableArea, layerForHorizontalScrollbar()).WillRepeatedly(Return(&graphicsLayer));
Scrollbar* scrollbar = Scrollbar::create(scrollableArea, HorizontalScrollbar, RegularScrollbar, nullptr);
graphicsLayer.resetTrackedPaintInvalidations();
scrollbar->setNeedsPaintInvalidation(NoPart);
EXPECT_TRUE(graphicsLayer.hasTrackedPaintInvalidations());
// Forced GC in order to finalize objects depending on the mock object.
Heap::collectAllGarbage();
}
TEST_F(ScrollableAreaTest, InvalidatesNonCompositedScrollbarsWhenThumbMoves)
{
ScrollbarThemeWithMockInvalidation theme;
MockScrollableArea* scrollableArea = MockScrollableArea::create(IntPoint(100, 100));
Scrollbar* horizontalScrollbar = Scrollbar::createForTesting(scrollableArea, HorizontalScrollbar, RegularScrollbar, &theme);
Scrollbar* verticalScrollbar = Scrollbar::createForTesting(scrollableArea, VerticalScrollbar, RegularScrollbar, &theme);
EXPECT_CALL(*scrollableArea, horizontalScrollbar()).WillRepeatedly(Return(horizontalScrollbar));
EXPECT_CALL(*scrollableArea, verticalScrollbar()).WillRepeatedly(Return(verticalScrollbar));
// Regardless of whether the theme invalidates any parts, non-composited
// scrollbars have to be repainted if the thumb moves.
EXPECT_CALL(*scrollableArea, layerForHorizontalScrollbar()).WillRepeatedly(Return(nullptr));
EXPECT_CALL(*scrollableArea, layerForVerticalScrollbar()).WillRepeatedly(Return(nullptr));
ASSERT_FALSE(scrollableArea->hasLayerForVerticalScrollbar());
ASSERT_FALSE(scrollableArea->hasLayerForHorizontalScrollbar());
EXPECT_CALL(theme, shouldRepaintAllPartsOnInvalidation()).WillRepeatedly(Return(false));
EXPECT_CALL(theme, invalidateOnThumbPositionChange(_, _, _)).WillRepeatedly(Return(NoPart));
// A scroll in each direction should only invalidate one scrollbar.
scrollableArea->setScrollPosition(DoublePoint(0, 50), ProgrammaticScroll);
EXPECT_FALSE(scrollableArea->horizontalScrollbarNeedsPaintInvalidation());
EXPECT_TRUE(scrollableArea->verticalScrollbarNeedsPaintInvalidation());
scrollableArea->clearNeedsPaintInvalidationForScrollControls();
scrollableArea->setScrollPosition(DoublePoint(50, 50), ProgrammaticScroll);
EXPECT_TRUE(scrollableArea->horizontalScrollbarNeedsPaintInvalidation());
EXPECT_FALSE(scrollableArea->verticalScrollbarNeedsPaintInvalidation());
scrollableArea->clearNeedsPaintInvalidationForScrollControls();
// Forced GC in order to finalize objects depending on the mock object.
Heap::collectAllGarbage();
}
TEST_F(ScrollableAreaTest, InvalidatesCompositedScrollbarsIfPartsNeedRepaint)
{
ScrollbarThemeWithMockInvalidation theme;
MockScrollableArea* scrollableArea = MockScrollableArea::create(IntPoint(100, 100));
Scrollbar* horizontalScrollbar = Scrollbar::createForTesting(scrollableArea, HorizontalScrollbar, RegularScrollbar, &theme);
horizontalScrollbar->clearTrackNeedsRepaint();
horizontalScrollbar->clearThumbNeedsRepaint();
Scrollbar* verticalScrollbar = Scrollbar::createForTesting(scrollableArea, VerticalScrollbar, RegularScrollbar, &theme);
verticalScrollbar->clearTrackNeedsRepaint();
verticalScrollbar->clearThumbNeedsRepaint();
EXPECT_CALL(*scrollableArea, horizontalScrollbar()).WillRepeatedly(Return(horizontalScrollbar));
EXPECT_CALL(*scrollableArea, verticalScrollbar()).WillRepeatedly(Return(verticalScrollbar));
// Composited scrollbars only need repainting when parts become invalid
// (e.g. if the track changes appearance when the thumb reaches the end).
MockGraphicsLayerClient graphicsLayerClient;
FakeGraphicsLayer layerForHorizontalScrollbar(&graphicsLayerClient);
layerForHorizontalScrollbar.setDrawsContent(true);
layerForHorizontalScrollbar.setSize(FloatSize(10, 10));
FakeGraphicsLayer layerForVerticalScrollbar(&graphicsLayerClient);
layerForVerticalScrollbar.setDrawsContent(true);
layerForVerticalScrollbar.setSize(FloatSize(10, 10));
EXPECT_CALL(*scrollableArea, layerForHorizontalScrollbar()).WillRepeatedly(Return(&layerForHorizontalScrollbar));
EXPECT_CALL(*scrollableArea, layerForVerticalScrollbar()).WillRepeatedly(Return(&layerForVerticalScrollbar));
ASSERT_TRUE(scrollableArea->hasLayerForHorizontalScrollbar());
ASSERT_TRUE(scrollableArea->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(BackButtonStartPart));
scrollableArea->setScrollPosition(DoublePoint(50, 0), ProgrammaticScroll);
EXPECT_TRUE(layerForHorizontalScrollbar.hasTrackedPaintInvalidations());
EXPECT_FALSE(layerForVerticalScrollbar.hasTrackedPaintInvalidations());
EXPECT_TRUE(horizontalScrollbar->trackNeedsRepaint());
EXPECT_FALSE(horizontalScrollbar->thumbNeedsRepaint());
layerForHorizontalScrollbar.resetTrackedPaintInvalidations();
horizontalScrollbar->clearTrackNeedsRepaint();
// Next, we'll scroll vertically, but invalidate the thumb.
EXPECT_CALL(theme, invalidateOnThumbPositionChange(_, _, _)).WillOnce(Return(ThumbPart));
scrollableArea->setScrollPosition(DoublePoint(50, 50), ProgrammaticScroll);
EXPECT_FALSE(layerForHorizontalScrollbar.hasTrackedPaintInvalidations());
EXPECT_TRUE(layerForVerticalScrollbar.hasTrackedPaintInvalidations());
EXPECT_FALSE(verticalScrollbar->trackNeedsRepaint());
EXPECT_TRUE(verticalScrollbar->thumbNeedsRepaint());
layerForVerticalScrollbar.resetTrackedPaintInvalidations();
verticalScrollbar->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(NoPart));
scrollableArea->setScrollPosition(DoublePoint(70, 70), ProgrammaticScroll);
EXPECT_TRUE(layerForHorizontalScrollbar.hasTrackedPaintInvalidations());
EXPECT_TRUE(layerForVerticalScrollbar.hasTrackedPaintInvalidations());
EXPECT_FALSE(horizontalScrollbar->trackNeedsRepaint());
EXPECT_FALSE(horizontalScrollbar->thumbNeedsRepaint());
EXPECT_FALSE(verticalScrollbar->trackNeedsRepaint());
EXPECT_FALSE(verticalScrollbar->thumbNeedsRepaint());
// Forced GC in order to finalize objects depending on the mock object.
Heap::collectAllGarbage();
}
TEST_F(ScrollableAreaTest, RecalculatesScrollbarOverlayIfBackgroundChanges)
{
MockScrollableArea* scrollableArea = MockScrollableArea::create(IntPoint(0, 100));
EXPECT_EQ(ScrollbarOverlayStyleDefault, scrollableArea->getScrollbarOverlayStyle());
scrollableArea->recalculateScrollbarOverlayStyle(Color(34, 85, 51));
EXPECT_EQ(ScrollbarOverlayStyleLight, scrollableArea->getScrollbarOverlayStyle());
scrollableArea->recalculateScrollbarOverlayStyle(Color(236, 143, 185));
EXPECT_EQ(ScrollbarOverlayStyleDefault, scrollableArea->getScrollbarOverlayStyle());
}
} // namespace blink