summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/animation/scroll_timeline_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/core/animation/scroll_timeline_test.cc')
-rw-r--r--chromium/third_party/blink/renderer/core/animation/scroll_timeline_test.cc254
1 files changed, 248 insertions, 6 deletions
diff --git a/chromium/third_party/blink/renderer/core/animation/scroll_timeline_test.cc b/chromium/third_party/blink/renderer/core/animation/scroll_timeline_test.cc
index ac3d1cd6811..bdc9d1c9dd8 100644
--- a/chromium/third_party/blink/renderer/core/animation/scroll_timeline_test.cc
+++ b/chromium/third_party/blink/renderer/core/animation/scroll_timeline_test.cc
@@ -11,6 +11,7 @@
#include "third_party/blink/renderer/core/animation/keyframe_effect.h"
#include "third_party/blink/renderer/core/animation/keyframe_effect_model.h"
#include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
+#include "third_party/blink/renderer/core/dom/dom_token_list.h"
#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
@@ -30,6 +31,13 @@ static constexpr double time_error_ms = 0.001 + 1e-13;
#define EXPECT_TIME_NEAR(expected, value) \
EXPECT_NEAR(expected, value, time_error_ms)
+void ExpectVectorDoubleEqual(const WTF::Vector<double>& expected,
+ const WTF::Vector<double>& value) {
+ EXPECT_EQ(expected.size(), value.size());
+ for (unsigned int i = 0; i < expected.size(); i++)
+ EXPECT_DOUBLE_EQ(expected[i], value[i]);
+}
+
HeapVector<Member<ScrollTimelineOffset>> CreateScrollOffsets(
ScrollTimelineOffset* start_scroll_offset =
MakeGarbageCollected<ScrollTimelineOffset>(
@@ -47,6 +55,17 @@ HeapVector<Member<ScrollTimelineOffset>> CreateScrollOffsets(
return scroll_offsets;
}
+Animation* CreateTestAnimation(AnimationTimeline* timeline) {
+ Timing timing;
+ timing.iteration_duration = AnimationTimeDelta::FromSecondsD(0.1);
+ return Animation::Create(MakeGarbageCollected<KeyframeEffect>(
+ nullptr,
+ MakeGarbageCollected<StringKeyframeEffectModel>(
+ StringKeyframeVector()),
+ timing),
+ timeline, ASSERT_NO_EXCEPTION);
+}
+
} // namespace
class ScrollTimelineTest : public RenderingTest {
@@ -149,8 +168,8 @@ TEST_F(ScrollTimelineTest,
DoubleOrScrollTimelineAutoKeyword::FromDouble(100);
options->setTimeRange(time_range);
options->setScrollSource(GetElementById("scroller"));
- options->setStartScrollOffset(OffsetFromString("10px"));
- options->setEndScrollOffset(OffsetFromString("90px"));
+ options->setScrollOffsets(
+ {OffsetFromString("10px"), OffsetFromString("90px")});
ScrollTimeline* scroll_timeline =
ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
@@ -218,8 +237,8 @@ TEST_F(ScrollTimelineTest,
DoubleOrScrollTimelineAutoKeyword::FromDouble(100);
options->setTimeRange(time_range);
options->setScrollSource(GetElementById("scroller"));
- options->setStartScrollOffset(OffsetFromString("80px"));
- options->setEndScrollOffset(OffsetFromString("40px"));
+ options->setScrollOffsets(
+ {OffsetFromString("80px"), OffsetFromString("40px")});
ScrollTimeline* scroll_timeline =
ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
@@ -271,8 +290,8 @@ TEST_F(ScrollTimelineTest, PhasesAreCorrectWhenUsingOffsets) {
DoubleOrScrollTimelineAutoKeyword::FromDouble(100);
options->setTimeRange(time_range);
options->setScrollSource(GetElementById("scroller"));
- options->setStartScrollOffset(OffsetFromString("10px"));
- options->setEndScrollOffset(OffsetFromString("90px"));
+ options->setScrollOffsets(
+ {OffsetFromString("10px"), OffsetFromString("90px")});
ScrollTimeline* scroll_timeline =
ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
@@ -435,6 +454,116 @@ TEST_F(ScrollTimelineTest, AnimationIsGarbageCollectedWhenScrollerIsRemoved) {
EXPECT_EQ(0u, AnimationsCount());
}
+TEST_F(ScrollTimelineTest, AnimationPersistsWhenFinished) {
+ SetBodyInnerHTML(R"HTML(
+ <style>
+ #scroller { overflow: scroll; width: 100px; height: 100px; }
+ #spacer { width: 200px; height: 200px; }
+ </style>
+ <div id='scroller'>
+ <div id ='spacer'></div>
+ </div>
+ )HTML");
+
+ auto* scroller =
+ To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
+ PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
+ Persistent<TestScrollTimeline> scroll_timeline =
+ MakeGarbageCollected<TestScrollTimeline>(&GetDocument(),
+ GetElementById("scroller"));
+ Animation* animation = CreateTestAnimation(scroll_timeline);
+ animation->play();
+ SimulateFrame();
+
+ // Scroll to finished:
+ scrollable_area->SetScrollOffset(ScrollOffset(0, 91),
+ mojom::blink::ScrollType::kProgrammatic);
+ SimulateFrame();
+ EXPECT_EQ("finished", animation->playState());
+
+ // Animation should still persist after GC.
+ animation = nullptr;
+ ThreadState::Current()->CollectAllGarbageForTesting();
+ ASSERT_EQ(1u, scroll_timeline->GetAnimations().size());
+ animation = *scroll_timeline->GetAnimations().begin();
+
+ // Scroll back to 50%. The animation should update, even though it was
+ // previously in a finished state.
+ ScrollOffset offset(0, 50); // 10 + (90 - 10) * 0.5 = 50
+ scrollable_area->SetScrollOffset(offset,
+ mojom::blink::ScrollType::kProgrammatic);
+ SimulateFrame();
+ EXPECT_EQ("running", animation->playState());
+ EXPECT_TIME_NEAR(50.0, animation->CurrentTimeInternal()
+ .value_or(AnimationTimeDelta())
+ .InMillisecondsF());
+}
+
+TEST_F(ScrollTimelineTest, AnimationPersistsWhenSourceBecomesNonScrollable) {
+ SetBodyInnerHTML(R"HTML(
+ <style>
+ #scroller { width: 100px; height: 100px; }
+ #spacer { width: 200px; height: 200px; }
+ .scroll { overflow: scroll; }
+ </style>
+ <div id='scroller' class='scroll'>
+ <div id ='spacer'></div>
+ </div>
+ )HTML");
+
+ auto* scroller =
+ To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
+ Persistent<TestScrollTimeline> scroll_timeline =
+ MakeGarbageCollected<TestScrollTimeline>(&GetDocument(),
+ GetElementById("scroller"));
+ Animation* animation = CreateTestAnimation(scroll_timeline);
+ animation->play();
+ SimulateFrame();
+
+ // Scroll to 50%:
+ ASSERT_TRUE(scroller->GetScrollableArea());
+ ScrollOffset offset_50(0, 50); // 10 + (90 - 10) * 0.5 = 50
+ scroller->GetScrollableArea()->SetScrollOffset(
+ offset_50, mojom::blink::ScrollType::kProgrammatic);
+ SimulateFrame();
+ EXPECT_TIME_NEAR(50.0, animation->CurrentTimeInternal()
+ .value_or(AnimationTimeDelta())
+ .InMillisecondsF());
+
+ // Make #scroller non-scrollable.
+ GetElementById("scroller")->classList().Remove("scroll");
+ UpdateAllLifecyclePhasesForTest();
+ scroller = To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
+ ASSERT_TRUE(scroller);
+ EXPECT_FALSE(scroller->GetScrollableArea());
+
+ // ScrollTimeline should now have an unresolved current time.
+ SimulateFrame();
+ EXPECT_FALSE(scroll_timeline->CurrentTimeMilliseconds().has_value());
+
+ // Animation should still persist after GC.
+ animation = nullptr;
+ ThreadState::Current()->CollectAllGarbageForTesting();
+ ASSERT_EQ(1u, scroll_timeline->GetAnimations().size());
+ animation = *scroll_timeline->GetAnimations().begin();
+
+ // Make #scroller scrollable again.
+ GetElementById("scroller")->classList().Add("scroll");
+ UpdateAllLifecyclePhasesForTest();
+ scroller = To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
+ ASSERT_TRUE(scroller);
+ ASSERT_TRUE(scroller->GetScrollableArea());
+
+ // Scroll to 40%:
+ ScrollOffset offset_42(0, 42); // 10 + (90 - 10) * 0.4 = 42
+ scroller->GetScrollableArea()->SetScrollOffset(
+ offset_42, mojom::blink::ScrollType::kProgrammatic);
+ SimulateFrame();
+ EXPECT_TIME_NEAR(40.0, animation->CurrentTimeInternal()
+ .value_or(AnimationTimeDelta())
+ .InMillisecondsF());
+}
+
TEST_F(ScrollTimelineTest, ScheduleFrameOnlyWhenScrollOffsetChanges) {
SetBodyInnerHTML(R"HTML(
<style>
@@ -786,6 +915,72 @@ TEST_F(ScrollTimelineTest,
EXPECT_FALSE(event_listener->EventReceived());
}
+TEST_F(ScrollTimelineTest, ResolveScrollOffsets) {
+ SetBodyInnerHTML(R"HTML(
+ <style>
+ #scroller { overflow: scroll; width: 100px; height: 100px; }
+ #spacer { height: 1000px; }
+ </style>
+ <div id='scroller'>
+ <div id ='spacer'></div>
+ </div>
+ )HTML");
+
+ auto* scroller =
+ To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
+ ASSERT_TRUE(scroller);
+ PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
+ ASSERT_TRUE(scrollable_area);
+ double time_range = 100.0;
+ ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
+ options->setTimeRange(
+ DoubleOrScrollTimelineAutoKeyword::FromDouble(time_range));
+ options->setScrollSource(GetElementById("scroller"));
+ // Empty scroll offsets resolve into [0, 100%].
+ HeapVector<ScrollTimelineOffsetValue> scroll_offsets = {};
+ options->setScrollOffsets(scroll_offsets);
+
+ ScrollTimeline* scroll_timeline =
+ ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
+
+ WTF::Vector<double> resolved_offsets;
+ WTF::Vector<double> expected_offsets = {0, 900.0};
+ scroll_timeline->ResolveScrollOffsets(resolved_offsets);
+ ExpectVectorDoubleEqual(expected_offsets, resolved_offsets);
+
+ // Single 'auto' offset resolve into [0, 100%].
+ scroll_offsets = {OffsetFromString("auto")};
+ options->setScrollOffsets(scroll_offsets);
+ scroll_timeline =
+ ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
+ resolved_offsets.clear();
+ scroll_timeline->ResolveScrollOffsets(resolved_offsets);
+ expected_offsets = {0, 900.0};
+ ExpectVectorDoubleEqual(expected_offsets, resolved_offsets);
+
+ // Start and end 'auto' offsets resolve into [0, 100%].
+ scroll_offsets = {OffsetFromString("auto"), OffsetFromString("auto")};
+ options->setScrollOffsets(scroll_offsets);
+ scroll_timeline =
+ ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
+ resolved_offsets.clear();
+ scroll_timeline->ResolveScrollOffsets(resolved_offsets);
+ expected_offsets = {0, 900.0};
+ ExpectVectorDoubleEqual(expected_offsets, resolved_offsets);
+
+ // Three offsets, start and end are 'auto' resolve into [0, middle offset,
+ // 100%].
+ scroll_offsets = {OffsetFromString("auto"), OffsetFromString("500px"),
+ OffsetFromString("auto")};
+ options->setScrollOffsets(scroll_offsets);
+ scroll_timeline =
+ ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
+ resolved_offsets.clear();
+ scroll_timeline->ResolveScrollOffsets(resolved_offsets);
+ expected_offsets = {0, 500.0, 900.0};
+ ExpectVectorDoubleEqual(expected_offsets, resolved_offsets);
+}
+
TEST_F(ScrollTimelineTest, MultipleScrollOffsetsCurrentTimeCalculations) {
SetBodyInnerHTML(R"HTML(
<style>
@@ -942,4 +1137,51 @@ TEST_F(ScrollTimelineTest, OverlappingScrollOffsets) {
EXPECT_EQ(80, scroll_timeline->CurrentTimeMilliseconds().value());
}
+TEST_F(ScrollTimelineTest, WeakReferences) {
+ SetBodyInnerHTML(R"HTML(
+ <style>
+ #scroller { overflow: scroll; width: 100px; height: 100px; }
+ #spacer { width: 200px; height: 200px; }
+ </style>
+ <div id='scroller'>
+ <div id ='spacer'></div>
+ </div>
+ )HTML");
+
+ Persistent<TestScrollTimeline> scroll_timeline =
+ MakeGarbageCollected<TestScrollTimeline>(&GetDocument(),
+ GetElementById("scroller"));
+
+ EXPECT_EQ(0u, scroll_timeline->GetAnimations().size());
+
+ // Attaching an animation to a ScrollTimeline, and never playing it:
+ Animation* animation = CreateTestAnimation(scroll_timeline);
+ DCHECK(animation);
+ animation = nullptr;
+ EXPECT_EQ(1u, scroll_timeline->GetAnimations().size());
+
+ ThreadState::Current()->CollectAllGarbageForTesting();
+ EXPECT_EQ(0u, scroll_timeline->GetAnimations().size());
+
+ // Playing, then canceling an animation:
+ animation = CreateTestAnimation(scroll_timeline);
+ EXPECT_EQ(1u, scroll_timeline->GetAnimations().size());
+
+ animation->play();
+ UpdateAllLifecyclePhasesForTest();
+ EXPECT_EQ(1u, scroll_timeline->GetAnimations().size());
+
+ animation->cancel();
+ // UpdateAllLifecyclePhasesForTest does not call Animation::Update with
+ // reason=kTimingUpdateForAnimationFrame, which is required in order to lose
+ // all strong references to the animation. Hence the explicit call to
+ // SimulateFrame().
+ SimulateFrame();
+ UpdateAllLifecyclePhasesForTest();
+ animation = nullptr;
+
+ ThreadState::Current()->CollectAllGarbageForTesting();
+ EXPECT_EQ(0u, scroll_timeline->GetAnimations().size());
+}
+
} // namespace blink