// Copyright 2018 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/animation/scroll_timeline.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
namespace blink {
using ScrollTimelineTest = RenderingTest;
TEST_F(ScrollTimelineTest,
AttachingAndDetachingAnimationCausesCompositingUpdate) {
EnableCompositing();
SetBodyInnerHTML(R"HTML(
)HTML");
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
ASSERT_TRUE(scroller);
// Invariant: the scroller is not composited by default.
EXPECT_EQ(DocumentLifecycle::kPaintClean,
GetDocument().Lifecycle().GetState());
EXPECT_EQ(kNotComposited, scroller->Layer()->GetCompositingState());
// Create the ScrollTimeline. This shouldn't cause the scrollSource to need
// compositing, as it isn't attached to any animation yet.
ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
DoubleOrScrollTimelineAutoKeyword time_range =
DoubleOrScrollTimelineAutoKeyword::FromDouble(100);
options->setTimeRange(time_range);
options->setScrollSource(GetElementById("scroller"));
ScrollTimeline* scroll_timeline =
ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
EXPECT_EQ(DocumentLifecycle::kPaintClean,
GetDocument().Lifecycle().GetState());
EXPECT_EQ(kNotComposited, scroller->Layer()->GetCompositingState());
// Now attach an animation. This should require a compositing update.
scroll_timeline->AttachAnimation();
UpdateAllLifecyclePhasesForTest();
EXPECT_NE(scroller->Layer()->GetCompositingState(), kNotComposited);
// Now detach an animation. This should again require a compositing update.
scroll_timeline->DetachAnimation();
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(scroller->Layer()->GetCompositingState(), kNotComposited);
}
TEST_F(ScrollTimelineTest, CurrentTimeIsNullIfScrollSourceIsNotScrollable) {
SetBodyInnerHTML(R"HTML(
)HTML");
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
ASSERT_TRUE(scroller);
ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
DoubleOrScrollTimelineAutoKeyword time_range =
DoubleOrScrollTimelineAutoKeyword::FromDouble(100);
options->setTimeRange(time_range);
options->setScrollSource(GetElementById("scroller"));
ScrollTimeline* scroll_timeline =
ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
bool current_time_is_null = false;
scroll_timeline->currentTime(current_time_is_null);
EXPECT_TRUE(current_time_is_null);
}
TEST_F(ScrollTimelineTest,
CurrentTimeIsNullIfScrollOffsetIsBeyondStartAndEndScrollOffset) {
SetBodyInnerHTML(R"HTML(
)HTML");
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
ASSERT_TRUE(scroller);
ASSERT_TRUE(scroller->HasOverflowClip());
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
ASSERT_TRUE(scrollable_area);
ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
DoubleOrScrollTimelineAutoKeyword time_range =
DoubleOrScrollTimelineAutoKeyword::FromDouble(100);
options->setTimeRange(time_range);
options->setScrollSource(GetElementById("scroller"));
options->setStartScrollOffset("10px");
options->setEndScrollOffset("90px");
ScrollTimeline* scroll_timeline =
ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
bool current_time_is_null = false;
scrollable_area->SetScrollOffset(ScrollOffset(0, 5), kProgrammaticScroll);
scroll_timeline->currentTime(current_time_is_null);
EXPECT_TRUE(current_time_is_null);
current_time_is_null = true;
scrollable_area->SetScrollOffset(ScrollOffset(0, 50), kProgrammaticScroll);
scroll_timeline->currentTime(current_time_is_null);
EXPECT_FALSE(current_time_is_null);
current_time_is_null = false;
scrollable_area->SetScrollOffset(ScrollOffset(0, 100), kProgrammaticScroll);
scroll_timeline->currentTime(current_time_is_null);
EXPECT_TRUE(current_time_is_null);
}
TEST_F(ScrollTimelineTest,
CurrentTimeIsNullIfEndScrollOffsetIsLessThanStartScrollOffset) {
SetBodyInnerHTML(R"HTML(
)HTML");
LayoutBoxModelObject* scroller =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"));
ASSERT_TRUE(scroller);
ASSERT_TRUE(scroller->HasOverflowClip());
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
ASSERT_TRUE(scrollable_area);
ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
DoubleOrScrollTimelineAutoKeyword time_range =
DoubleOrScrollTimelineAutoKeyword::FromDouble(100);
options->setTimeRange(time_range);
options->setScrollSource(GetElementById("scroller"));
options->setStartScrollOffset("80px");
options->setEndScrollOffset("40px");
ScrollTimeline* scroll_timeline =
ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
bool current_time_is_null = false;
scrollable_area->SetScrollOffset(ScrollOffset(0, 50), kProgrammaticScroll);
scroll_timeline->currentTime(current_time_is_null);
EXPECT_TRUE(current_time_is_null);
}
TEST_F(ScrollTimelineTest,
UsingDocumentScrollingElementShouldCorrectlyResolveToDocument) {
SetBodyInnerHTML(R"HTML(
)HTML");
EXPECT_EQ(GetDocument().documentElement(), GetDocument().scrollingElement());
// Create the ScrollTimeline with Document.scrollingElement() as source. The
// resolved scroll source should be the Document.
ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
DoubleOrScrollTimelineAutoKeyword time_range =
DoubleOrScrollTimelineAutoKeyword::FromDouble(100);
options->setTimeRange(time_range);
options->setScrollSource(GetDocument().scrollingElement());
ScrollTimeline* scroll_timeline =
ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
EXPECT_EQ(&GetDocument(), scroll_timeline->ResolvedScrollSource());
}
TEST_F(ScrollTimelineTest,
ChangingDocumentScrollingElementShouldNotImpactScrollTimeline) {
SetBodyInnerHTML(R"HTML(
)HTML");
// In QuirksMode, the body is the scrolling element
GetDocument().SetCompatibilityMode(Document::kQuirksMode);
EXPECT_EQ(GetDocument().body(), GetDocument().scrollingElement());
// Create the ScrollTimeline with Document.scrollingElement() as source. The
// resolved scroll source should be the Document.
ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
DoubleOrScrollTimelineAutoKeyword time_range =
DoubleOrScrollTimelineAutoKeyword::FromDouble(100);
options->setTimeRange(time_range);
options->setScrollSource(GetDocument().scrollingElement());
ScrollTimeline* scroll_timeline =
ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
EXPECT_EQ(&GetDocument(), scroll_timeline->ResolvedScrollSource());
// Now change the Document.scrollingElement(). In NoQuirksMode, the
// documentElement is the scrolling element and not the body.
GetDocument().SetCompatibilityMode(Document::kNoQuirksMode);
EXPECT_NE(GetDocument().documentElement(), GetDocument().body());
EXPECT_EQ(GetDocument().documentElement(), GetDocument().scrollingElement());
// Changing the scrollingElement should not impact the previously resolved
// scroll source. Note that at this point the scroll timeline's scroll source
// is still body element which is no longer the scrolling element. So if we
// were to re-resolve the scroll source, it would not map to Document.
EXPECT_EQ(&GetDocument(), scroll_timeline->ResolvedScrollSource());
}
TEST_F(ScrollTimelineTest, AttachOrDetachAnimationWithNullScrollSource) {
// Directly call the constructor to make it easier to pass a null
// scrollSource. The alternative approach would require us to remove the
// documentElement from the document.
Element* scroll_source = nullptr;
CSSPrimitiveValue* start_scroll_offset = nullptr;
CSSPrimitiveValue* end_scroll_offset = nullptr;
ScrollTimeline* scroll_timeline = MakeGarbageCollected(
scroll_source, ScrollTimeline::Block, start_scroll_offset,
end_scroll_offset, 100);
// Sanity checks.
ASSERT_EQ(scroll_timeline->scrollSource(), nullptr);
ASSERT_EQ(scroll_timeline->ResolvedScrollSource(), nullptr);
// These calls should be no-ops in this mode, and shouldn't crash.
scroll_timeline->AttachAnimation();
scroll_timeline->DetachAnimation();
}
} // namespace blink