blob: 15f774f2c156c9acdb5daa2d710660f75d7178d4 [file] [log] [blame]
// Copyright 2014 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/core/frame/root_frame_viewport.h"
#include "base/single_thread_task_runner.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/scroll/scroll_alignment.h"
#include "third_party/blink/renderer/core/scroll/scroll_types.h"
#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_mock.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/geometry/double_rect.h"
#include "third_party/blink/renderer/platform/geometry/layout_rect.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
namespace blink {
class ScrollableAreaStub : public GarbageCollected<ScrollableAreaStub>,
public ScrollableArea {
public:
ScrollableAreaStub(const IntSize& viewport_size, const IntSize& contents_size)
: ScrollableArea(blink::scheduler::GetSingleThreadTaskRunnerForTesting()),
user_input_scrollable_x_(true),
user_input_scrollable_y_(true),
viewport_size_(viewport_size),
contents_size_(contents_size),
timer_task_runner_(
blink::scheduler::GetSingleThreadTaskRunnerForTesting()) {}
void SetViewportSize(const IntSize& viewport_size) {
viewport_size_ = viewport_size;
}
IntSize ViewportSize() const { return viewport_size_; }
// ScrollableArea Impl
int ScrollSize(ScrollbarOrientation orientation) const override {
IntSize scroll_dimensions =
MaximumScrollOffsetInt() - MinimumScrollOffsetInt();
return (orientation == kHorizontalScrollbar) ? scroll_dimensions.Width()
: scroll_dimensions.Height();
}
void SetUserInputScrollable(bool x, bool y) {
user_input_scrollable_x_ = x;
user_input_scrollable_y_ = y;
}
IntSize ScrollOffsetInt() const override {
return FlooredIntSize(scroll_offset_);
}
ScrollOffset GetScrollOffset() const override { return scroll_offset_; }
IntSize MinimumScrollOffsetInt() const override { return IntSize(); }
ScrollOffset MinimumScrollOffset() const override { return ScrollOffset(); }
IntSize MaximumScrollOffsetInt() const override {
return FlooredIntSize(MaximumScrollOffset());
}
IntRect VisibleContentRect(
IncludeScrollbarsInRect = kExcludeScrollbars) const override {
return IntRect(IntPoint(FlooredIntSize(scroll_offset_)), viewport_size_);
}
IntSize ContentsSize() const override { return contents_size_; }
void SetContentSize(const IntSize& contents_size) {
contents_size_ = contents_size;
}
scoped_refptr<base::SingleThreadTaskRunner> GetTimerTaskRunner() const final {
return timer_task_runner_;
}
ScrollbarTheme& GetPageScrollbarTheme() const override {
DEFINE_STATIC_LOCAL(ScrollbarThemeOverlayMock, theme, ());
return theme;
}
bool ScrollAnimatorEnabled() const override { return true; }
void Trace(Visitor* visitor) const override {
ScrollableArea::Trace(visitor);
}
protected:
CompositorElementId GetScrollElementId() const override {
return CompositorElementId();
}
void UpdateScrollOffset(const ScrollOffset& offset,
mojom::blink::ScrollType) override {
scroll_offset_ = offset;
}
bool ShouldUseIntegerScrollOffset() const override { return true; }
bool IsThrottled() const override { return false; }
bool IsActive() const override { return true; }
bool IsScrollCornerVisible() const override { return true; }
IntRect ScrollCornerRect() const override { return IntRect(); }
bool ScrollbarsCanBeActive() const override { return true; }
bool ShouldPlaceVerticalScrollbarOnLeft() const override { return true; }
void ScrollControlWasSetNeedsPaintInvalidation() override {}
bool UserInputScrollable(ScrollbarOrientation orientation) const override {
return orientation == kHorizontalScrollbar ? user_input_scrollable_x_
: user_input_scrollable_y_;
}
bool ScheduleAnimation() override { return true; }
mojom::blink::ColorScheme UsedColorScheme() const override {
return ComputedStyle::InitialStyle().UsedColorScheme();
}
ScrollOffset ClampedScrollOffset(const ScrollOffset& offset) {
ScrollOffset min_offset = MinimumScrollOffset();
ScrollOffset max_offset = MaximumScrollOffset();
float width = std::min(std::max(offset.Width(), min_offset.Width()),
max_offset.Width());
float height = std::min(std::max(offset.Height(), min_offset.Height()),
max_offset.Height());
return ScrollOffset(width, height);
}
bool user_input_scrollable_x_;
bool user_input_scrollable_y_;
ScrollOffset scroll_offset_;
IntSize viewport_size_;
IntSize contents_size_;
scoped_refptr<base::SingleThreadTaskRunner> timer_task_runner_;
};
class RootLayoutViewportStub : public ScrollableAreaStub {
public:
RootLayoutViewportStub(const IntSize& viewport_size,
const IntSize& contents_size)
: ScrollableAreaStub(viewport_size, contents_size) {}
ScrollOffset MaximumScrollOffset() const override {
return ScrollOffset(ContentsSize() - ViewportSize());
}
PhysicalRect DocumentToFrame(const PhysicalRect& rect) const {
PhysicalRect ret = rect;
ret.Move(-PhysicalOffset::FromFloatSizeRound(GetScrollOffset()));
return ret;
}
private:
int VisibleWidth() const override { return viewport_size_.Width(); }
int VisibleHeight() const override { return viewport_size_.Height(); }
};
class VisualViewportStub : public ScrollableAreaStub {
public:
VisualViewportStub(const IntSize& viewport_size, const IntSize& contents_size)
: ScrollableAreaStub(viewport_size, contents_size), scale_(1) {}
ScrollOffset MaximumScrollOffset() const override {
ScrollOffset visible_viewport(ViewportSize());
visible_viewport.Scale(1 / scale_);
ScrollOffset max_offset = ScrollOffset(ContentsSize()) - visible_viewport;
return ScrollOffset(max_offset);
}
void SetScale(float scale) { scale_ = scale; }
private:
int VisibleWidth() const override { return viewport_size_.Width() / scale_; }
int VisibleHeight() const override {
return viewport_size_.Height() / scale_;
}
IntRect VisibleContentRect(IncludeScrollbarsInRect) const override {
FloatSize size(viewport_size_);
size.Scale(1 / scale_);
return IntRect(IntPoint(FlooredIntSize(GetScrollOffset())),
ExpandedIntSize(size));
}
float scale_;
};
class RootFrameViewportTest : public testing::Test {
public:
RootFrameViewportTest() = default;
protected:
void SetUp() override {}
};
// Tests that scrolling the viewport when the layout viewport is
// !userInputScrollable (as happens when overflow:hidden is set) works
// correctly, that is, the visual viewport can scroll, but not the layout.
TEST_F(RootFrameViewportTest, UserInputScrollable) {
IntSize viewport_size(100, 150);
auto* layout_viewport = MakeGarbageCollected<RootLayoutViewportStub>(
viewport_size, IntSize(200, 300));
auto* visual_viewport =
MakeGarbageCollected<VisualViewportStub>(viewport_size, viewport_size);
auto* root_frame_viewport = MakeGarbageCollected<RootFrameViewport>(
*visual_viewport, *layout_viewport);
visual_viewport->SetScale(2);
// Disable just the layout viewport's horizontal scrolling, the
// RootFrameViewport should remain scrollable overall.
layout_viewport->SetUserInputScrollable(false, true);
visual_viewport->SetUserInputScrollable(true, true);
EXPECT_TRUE(root_frame_viewport->UserInputScrollable(kHorizontalScrollbar));
EXPECT_TRUE(root_frame_viewport->UserInputScrollable(kVerticalScrollbar));
// Layout viewport shouldn't scroll since it's not horizontally scrollable,
// but visual viewport should.
root_frame_viewport->UserScroll(ScrollGranularity::kScrollByPrecisePixel,
FloatSize(300, 0),
ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(0, 0), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(50, 0), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(50, 0), root_frame_viewport->GetScrollOffset());
// Vertical scrolling should be unaffected.
root_frame_viewport->UserScroll(ScrollGranularity::kScrollByPrecisePixel,
FloatSize(0, 300),
ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(0, 150), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(50, 75), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(50, 225), root_frame_viewport->GetScrollOffset());
// Try the same checks as above but for the vertical direction.
// ===============================================
root_frame_viewport->SetScrollOffset(
ScrollOffset(), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback());
// Disable just the layout viewport's vertical scrolling, the
// RootFrameViewport should remain scrollable overall.
layout_viewport->SetUserInputScrollable(true, false);
visual_viewport->SetUserInputScrollable(true, true);
EXPECT_TRUE(root_frame_viewport->UserInputScrollable(kHorizontalScrollbar));
EXPECT_TRUE(root_frame_viewport->UserInputScrollable(kVerticalScrollbar));
// Layout viewport shouldn't scroll since it's not vertically scrollable,
// but visual viewport should.
root_frame_viewport->UserScroll(ScrollGranularity::kScrollByPrecisePixel,
FloatSize(0, 300),
ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(0, 0), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 75), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 75), root_frame_viewport->GetScrollOffset());
// Horizontal scrolling should be unaffected.
root_frame_viewport->UserScroll(ScrollGranularity::kScrollByPrecisePixel,
FloatSize(300, 0),
ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(100, 0), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(50, 75), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(150, 75), root_frame_viewport->GetScrollOffset());
}
// Make sure scrolls using the scroll animator (scroll(), setScrollOffset())
// work correctly when one of the subviewports is explicitly scrolled without
// using the // RootFrameViewport interface.
TEST_F(RootFrameViewportTest, TestScrollAnimatorUpdatedBeforeScroll) {
IntSize viewport_size(100, 150);
auto* layout_viewport = MakeGarbageCollected<RootLayoutViewportStub>(
viewport_size, IntSize(200, 300));
auto* visual_viewport =
MakeGarbageCollected<VisualViewportStub>(viewport_size, viewport_size);
auto* root_frame_viewport = MakeGarbageCollected<RootFrameViewport>(
*visual_viewport, *layout_viewport);
visual_viewport->SetScale(2);
visual_viewport->SetScrollOffset(ScrollOffset(50, 75),
mojom::blink::ScrollType::kProgrammatic);
EXPECT_EQ(ScrollOffset(50, 75), root_frame_viewport->GetScrollOffset());
// If the scroll animator doesn't update, it will still think it's at (0, 0)
// and so it may early exit.
root_frame_viewport->SetScrollOffset(
ScrollOffset(0, 0), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(0, 0), root_frame_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 0), visual_viewport->GetScrollOffset());
// Try again for userScroll()
visual_viewport->SetScrollOffset(ScrollOffset(50, 75),
mojom::blink::ScrollType::kProgrammatic);
EXPECT_EQ(ScrollOffset(50, 75), root_frame_viewport->GetScrollOffset());
root_frame_viewport->UserScroll(ScrollGranularity::kScrollByPrecisePixel,
FloatSize(-50, 0),
ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(0, 75), root_frame_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 75), visual_viewport->GetScrollOffset());
// Make sure the layout viewport is also accounted for.
root_frame_viewport->SetScrollOffset(
ScrollOffset(0, 0), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback());
layout_viewport->SetScrollOffset(ScrollOffset(100, 150),
mojom::blink::ScrollType::kProgrammatic);
EXPECT_EQ(ScrollOffset(100, 150), root_frame_viewport->GetScrollOffset());
root_frame_viewport->UserScroll(ScrollGranularity::kScrollByPrecisePixel,
FloatSize(-100, 0),
ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(0, 150), root_frame_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 150), layout_viewport->GetScrollOffset());
}
// Test that the scrollIntoView correctly scrolls the main frame
// and visual viewport such that the given rect is centered in the viewport.
TEST_F(RootFrameViewportTest, ScrollIntoView) {
IntSize viewport_size(100, 150);
auto* layout_viewport = MakeGarbageCollected<RootLayoutViewportStub>(
viewport_size, IntSize(200, 300));
auto* visual_viewport =
MakeGarbageCollected<VisualViewportStub>(viewport_size, viewport_size);
auto* root_frame_viewport = MakeGarbageCollected<RootFrameViewport>(
*visual_viewport, *layout_viewport);
// Test that the visual viewport is scrolled if the viewport has been
// resized (as is the case when the ChromeOS keyboard comes up) but not
// scaled.
visual_viewport->SetViewportSize(IntSize(100, 100));
root_frame_viewport->ScrollIntoView(
layout_viewport->DocumentToFrame(PhysicalRect(100, 250, 50, 50)),
ScrollAlignment::CreateScrollIntoViewParams(
ScrollAlignment::ToEdgeIfNeeded(), ScrollAlignment::ToEdgeIfNeeded(),
mojom::blink::ScrollType::kProgrammatic, true,
mojom::blink::ScrollBehavior::kInstant));
EXPECT_EQ(ScrollOffset(50, 150), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 50), visual_viewport->GetScrollOffset());
root_frame_viewport->ScrollIntoView(
layout_viewport->DocumentToFrame(PhysicalRect(25, 75, 50, 50)),
ScrollAlignment::CreateScrollIntoViewParams(
ScrollAlignment::ToEdgeIfNeeded(), ScrollAlignment::ToEdgeIfNeeded(),
mojom::blink::ScrollType::kProgrammatic, true,
mojom::blink::ScrollBehavior::kInstant));
EXPECT_EQ(ScrollOffset(25, 75), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 0), visual_viewport->GetScrollOffset());
// Reset the visual viewport's size, scale the page, and repeat the test
visual_viewport->SetViewportSize(IntSize(100, 150));
visual_viewport->SetScale(2);
root_frame_viewport->SetScrollOffset(
ScrollOffset(), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback());
root_frame_viewport->ScrollIntoView(
layout_viewport->DocumentToFrame(PhysicalRect(50, 75, 50, 75)),
ScrollAlignment::CreateScrollIntoViewParams(
ScrollAlignment::ToEdgeIfNeeded(), ScrollAlignment::ToEdgeIfNeeded(),
mojom::blink::ScrollType::kProgrammatic, true,
mojom::blink::ScrollBehavior::kInstant));
EXPECT_EQ(ScrollOffset(0, 0), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(50, 75), visual_viewport->GetScrollOffset());
root_frame_viewport->ScrollIntoView(
layout_viewport->DocumentToFrame(PhysicalRect(190, 290, 10, 10)),
ScrollAlignment::CreateScrollIntoViewParams(
ScrollAlignment::ToEdgeIfNeeded(), ScrollAlignment::ToEdgeIfNeeded(),
mojom::blink::ScrollType::kProgrammatic, true,
mojom::blink::ScrollBehavior::kInstant));
EXPECT_EQ(ScrollOffset(100, 150), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(50, 75), visual_viewport->GetScrollOffset());
// Scrolling into view the viewport rect itself should be a no-op.
visual_viewport->SetViewportSize(IntSize(100, 100));
visual_viewport->SetScale(1.5f);
visual_viewport->SetScrollOffset(ScrollOffset(0, 10),
mojom::blink::ScrollType::kProgrammatic);
layout_viewport->SetScrollOffset(ScrollOffset(50, 50),
mojom::blink::ScrollType::kProgrammatic);
root_frame_viewport->SetScrollOffset(root_frame_viewport->GetScrollOffset(),
mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant,
ScrollableArea::ScrollCallback());
root_frame_viewport->ScrollIntoView(
layout_viewport->DocumentToFrame(PhysicalRect(
root_frame_viewport->VisibleContentRect(kExcludeScrollbars))),
ScrollAlignment::CreateScrollIntoViewParams(
ScrollAlignment::ToEdgeIfNeeded(), ScrollAlignment::ToEdgeIfNeeded(),
mojom::blink::ScrollType::kProgrammatic, true,
mojom::blink::ScrollBehavior::kInstant));
EXPECT_EQ(ScrollOffset(50, 50), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 10), visual_viewport->GetScrollOffset());
root_frame_viewport->ScrollIntoView(
layout_viewport->DocumentToFrame(PhysicalRect(
root_frame_viewport->VisibleContentRect(kExcludeScrollbars))),
ScrollAlignment::CreateScrollIntoViewParams(
ScrollAlignment::CenterAlways(), ScrollAlignment::CenterAlways(),
mojom::blink::ScrollType::kProgrammatic, true,
mojom::blink::ScrollBehavior::kInstant));
EXPECT_EQ(ScrollOffset(50, 50), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 10), visual_viewport->GetScrollOffset());
root_frame_viewport->ScrollIntoView(
layout_viewport->DocumentToFrame(PhysicalRect(
root_frame_viewport->VisibleContentRect(kExcludeScrollbars))),
ScrollAlignment::CreateScrollIntoViewParams(
ScrollAlignment::TopAlways(), ScrollAlignment::TopAlways(),
mojom::blink::ScrollType::kProgrammatic, true,
mojom::blink::ScrollBehavior::kInstant));
EXPECT_EQ(ScrollOffset(50, 50), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 10), visual_viewport->GetScrollOffset());
}
// Tests that the setScrollOffset method works correctly with both viewports.
TEST_F(RootFrameViewportTest, SetScrollOffset) {
IntSize viewport_size(500, 500);
auto* layout_viewport = MakeGarbageCollected<RootLayoutViewportStub>(
viewport_size, IntSize(1000, 2000));
auto* visual_viewport =
MakeGarbageCollected<VisualViewportStub>(viewport_size, viewport_size);
auto* root_frame_viewport = MakeGarbageCollected<RootFrameViewport>(
*visual_viewport, *layout_viewport);
visual_viewport->SetScale(2);
// Ensure that the visual viewport scrolls first.
root_frame_viewport->SetScrollOffset(
ScrollOffset(100, 100), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(100, 100), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 0), layout_viewport->GetScrollOffset());
// Scroll to the visual viewport's extent, the layout viewport should scroll
// the remainder.
root_frame_viewport->SetScrollOffset(
ScrollOffset(300, 400), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(250, 250), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(50, 150), layout_viewport->GetScrollOffset());
// Only the layout viewport should scroll further. Make sure it doesn't scroll
// out of bounds.
root_frame_viewport->SetScrollOffset(
ScrollOffset(780, 1780), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(250, 250), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(500, 1500), layout_viewport->GetScrollOffset());
// Scroll all the way back.
root_frame_viewport->SetScrollOffset(
ScrollOffset(0, 0), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(0, 0), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 0), layout_viewport->GetScrollOffset());
}
// Tests that the visible rect (i.e. visual viewport rect) is correctly
// calculated, taking into account both viewports and page scale.
TEST_F(RootFrameViewportTest, VisibleContentRect) {
IntSize viewport_size(500, 401);
auto* layout_viewport = MakeGarbageCollected<RootLayoutViewportStub>(
viewport_size, IntSize(1000, 2000));
auto* visual_viewport =
MakeGarbageCollected<VisualViewportStub>(viewport_size, viewport_size);
auto* root_frame_viewport = MakeGarbageCollected<RootFrameViewport>(
*visual_viewport, *layout_viewport);
root_frame_viewport->SetScrollOffset(
ScrollOffset(100, 75), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback());
EXPECT_EQ(IntPoint(100, 75),
root_frame_viewport->VisibleContentRect().Location());
EXPECT_EQ(ScrollOffset(500, 401),
DoubleSize(root_frame_viewport->VisibleContentRect().Size()));
visual_viewport->SetScale(2);
EXPECT_EQ(IntPoint(100, 75),
root_frame_viewport->VisibleContentRect().Location());
EXPECT_EQ(ScrollOffset(250, 201),
DoubleSize(root_frame_viewport->VisibleContentRect().Size()));
}
// Tests that scrolls on the root frame scroll the visual viewport before
// trying to scroll the layout viewport.
TEST_F(RootFrameViewportTest, ViewportScrollOrder) {
IntSize viewport_size(100, 100);
auto* layout_viewport = MakeGarbageCollected<RootLayoutViewportStub>(
viewport_size, IntSize(200, 300));
auto* visual_viewport =
MakeGarbageCollected<VisualViewportStub>(viewport_size, viewport_size);
auto* root_frame_viewport = MakeGarbageCollected<RootFrameViewport>(
*visual_viewport, *layout_viewport);
visual_viewport->SetScale(2);
root_frame_viewport->SetScrollOffset(
ScrollOffset(40, 40), mojom::blink::ScrollType::kUser,
mojom::blink::ScrollBehavior::kInstant,
ScrollableArea::ScrollCallback(base::BindOnce(
[](ScrollableArea* visual_viewport, ScrollableArea* layout_viewport) {
EXPECT_EQ(ScrollOffset(40, 40), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 0), layout_viewport->GetScrollOffset());
},
visual_viewport, layout_viewport)));
EXPECT_EQ(ScrollOffset(40, 40), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 0), layout_viewport->GetScrollOffset());
root_frame_viewport->SetScrollOffset(
ScrollOffset(60, 60), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant,
ScrollableArea::ScrollCallback(base::BindOnce(
[](ScrollableArea* visual_viewport, ScrollableArea* layout_viewport) {
EXPECT_EQ(ScrollOffset(50, 50), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(10, 10), layout_viewport->GetScrollOffset());
},
visual_viewport, layout_viewport)));
EXPECT_EQ(ScrollOffset(50, 50), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(10, 10), layout_viewport->GetScrollOffset());
}
// Tests that setting an alternate layout viewport scrolls the alternate
// instead of the original.
TEST_F(RootFrameViewportTest, SetAlternateLayoutViewport) {
IntSize viewport_size(100, 100);
auto* layout_viewport = MakeGarbageCollected<RootLayoutViewportStub>(
viewport_size, IntSize(200, 300));
auto* visual_viewport =
MakeGarbageCollected<VisualViewportStub>(viewport_size, viewport_size);
auto* alternate_scroller = MakeGarbageCollected<RootLayoutViewportStub>(
viewport_size, IntSize(600, 500));
auto* root_frame_viewport = MakeGarbageCollected<RootFrameViewport>(
*visual_viewport, *layout_viewport);
visual_viewport->SetScale(2);
root_frame_viewport->SetScrollOffset(
ScrollOffset(100, 100), mojom::blink::ScrollType::kUser,
mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(50, 50), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(50, 50), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(100, 100), root_frame_viewport->GetScrollOffset());
root_frame_viewport->SetLayoutViewport(*alternate_scroller);
EXPECT_EQ(ScrollOffset(50, 50), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 0), alternate_scroller->GetScrollOffset());
EXPECT_EQ(ScrollOffset(50, 50), root_frame_viewport->GetScrollOffset());
root_frame_viewport->SetScrollOffset(
ScrollOffset(200, 200), mojom::blink::ScrollType::kUser,
mojom::blink::ScrollBehavior::kInstant, ScrollableArea::ScrollCallback());
EXPECT_EQ(ScrollOffset(50, 50), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(150, 150), alternate_scroller->GetScrollOffset());
EXPECT_EQ(ScrollOffset(200, 200), root_frame_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(50, 50), layout_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(550, 450), root_frame_viewport->MaximumScrollOffset());
}
// Tests that scrolls on the root frame scroll the visual viewport before
// trying to scroll the layout viewport when using
// DistributeScrollBetweenViewports directly.
TEST_F(RootFrameViewportTest, DistributeScrollOrder) {
IntSize viewport_size(100, 100);
auto* layout_viewport = MakeGarbageCollected<RootLayoutViewportStub>(
viewport_size, IntSize(200, 300));
auto* visual_viewport =
MakeGarbageCollected<VisualViewportStub>(viewport_size, viewport_size);
auto* root_frame_viewport = MakeGarbageCollected<RootFrameViewport>(
*visual_viewport, *layout_viewport);
visual_viewport->SetScale(2);
root_frame_viewport->DistributeScrollBetweenViewports(
ScrollOffset(60, 60), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kSmooth, RootFrameViewport::kVisualViewport,
ScrollableArea::ScrollCallback(base::BindOnce(
[](ScrollableArea* visual_viewport, ScrollableArea* layout_viewport) {
EXPECT_EQ(ScrollOffset(50, 50), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(10, 10), layout_viewport->GetScrollOffset());
},
visual_viewport, layout_viewport)));
root_frame_viewport->UpdateCompositorScrollAnimations();
root_frame_viewport->ServiceScrollAnimations(1);
EXPECT_EQ(ScrollOffset(0, 0), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(0, 0), layout_viewport->GetScrollOffset());
root_frame_viewport->ServiceScrollAnimations(1000000);
EXPECT_EQ(ScrollOffset(50, 50), visual_viewport->GetScrollOffset());
EXPECT_EQ(ScrollOffset(10, 10), layout_viewport->GetScrollOffset());
}
class RootFrameViewportRenderTest : public RenderingTest {
public:
RootFrameViewportRenderTest()
: RenderingTest(MakeGarbageCollected<EmptyLocalFrameClient>()) {}
};
TEST_F(RootFrameViewportRenderTest,
ApplyPendingHistoryRestoreScrollOffsetTwice) {
HistoryItem::ViewState view_state;
view_state.page_scale_factor_ = 1.5;
RootFrameViewport* root_frame_viewport = static_cast<RootFrameViewport*>(
GetDocument().View()->GetScrollableArea());
root_frame_viewport->SetPendingHistoryRestoreScrollOffset(view_state, false);
root_frame_viewport->ApplyPendingHistoryRestoreScrollOffset();
// Override the 1.5 scale with 1.0.
GetDocument().GetPage()->GetVisualViewport().SetScale(1.0f);
// The second call to ApplyPendingHistoryRestoreScrollOffset should
// do nothing, since the history was already restored.
root_frame_viewport->ApplyPendingHistoryRestoreScrollOffset();
EXPECT_EQ(1.0f, GetDocument().GetPage()->GetVisualViewport().Scale());
}
} // namespace blink