diff options
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.cc | 254 |
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 |