Skip to content

Commit

Permalink
Removed scroll start and end offset properties of ScrollTimeline
Browse files Browse the repository at this point in the history
Made modifications spec'ed in
w3c/csswg-drafts#5803:
- Removed startOffset and endOffset attributes.
- Updated procedures based on spec changes, including allowing single
scroll offset of 'auto'.

Bug: 1094014
Change-Id: Id08c9ce1818bd561ac291ca3e1f99c22dbb15674
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2801290
Commit-Queue: Olga Gerchikov <gerchiko@microsoft.com>
Reviewed-by: Robert Flack <flackr@chromium.org>
Reviewed-by: Kevin Ellis <kevers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#874955}
GitOrigin-RevId: c9eb819fca15eca5ba6409ed4135247cbcce2bf5
  • Loading branch information
ogerchikov authored and copybara-github committed Apr 22, 2021
1 parent bd6de86 commit 1c4cc8d
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 104 deletions.
151 changes: 71 additions & 80 deletions blink/renderer/core/animation/scroll_timeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -114,58 +114,25 @@ ScrollTimeline* ScrollTimeline::Create(Document& document,
return nullptr;
}

ScrollTimelineOffset* start_scroll_offset =
ScrollTimelineOffset::Create(options->startScrollOffset());
if (!start_scroll_offset) {
exception_state.ThrowTypeError("Invalid start offset.");
return nullptr;
}

ScrollTimelineOffset* end_scroll_offset =
ScrollTimelineOffset::Create(options->endScrollOffset());
if (!end_scroll_offset) {
exception_state.ThrowTypeError("Invalid end offset");
return nullptr;
}

// TODO(crbug.com/1094014): Either scroll offsets or start/end offsets can
// be specified.
if (!options->scrollOffsets().IsEmpty() &&
(!start_scroll_offset->IsDefaultValue() ||
!end_scroll_offset->IsDefaultValue())) {
exception_state.ThrowTypeError(
"Either scrollOffsets or start/end offsets can be specified.");
return nullptr;
}

HeapVector<Member<ScrollTimelineOffset>> scroll_offsets;
if (options->scrollOffsets().IsEmpty()) {
// TODO(crbug.com/1094014): scroll_offsets will replace start and end
// offsets once spec decision on multiple scroll offsets is finalized.
// https://github.com/w3c/csswg-drafts/issues/4912
if (!start_scroll_offset->IsDefaultValue())
scroll_offsets.push_back(start_scroll_offset);
if (!end_scroll_offset->IsDefaultValue() ||
!start_scroll_offset->IsDefaultValue())
scroll_offsets.push_back(end_scroll_offset);
} else {
for (auto& offset : options->scrollOffsets()) {
ScrollTimelineOffset* scroll_offset =
ScrollTimelineOffset::Create(offset);
if (!scroll_offset) {
exception_state.ThrowTypeError("Invalid scroll offset");
return nullptr;
}
if (scroll_offset->IsDefaultValue() &&
(options->scrollOffsets().size() == 1 ||
(scroll_offsets.size() + 1) < options->scrollOffsets().size())) {
exception_state.ThrowTypeError(
"Invalid scrollOffsets: 'auto' can only be set as an end "
"offset when start offset presents.");
return nullptr;
}
scroll_offsets.push_back(scroll_offset);
// https://drafts.csswg.org/scroll-animations-1/#set-the-offset-value
for (auto& offset : options->scrollOffsets()) {
ScrollTimelineOffset* scroll_offset = ScrollTimelineOffset::Create(offset);
if (!scroll_offset) {
exception_state.ThrowTypeError("Invalid scroll offset");
return nullptr;
}
// 2.1 If val is a CSSKeywordValue and matches the grammar auto and pos
// equals to 0 or size - 1: Return val.
unsigned int pos = scroll_offsets.size();
if (scroll_offset->IsDefaultValue() &&
!(pos == 0 || pos == (options->scrollOffsets().size() - 1))) {
exception_state.ThrowTypeError(
"Invalid scrollOffsets: 'auto' can only be set as start or end "
"offset");
return nullptr;
}
scroll_offsets.push_back(scroll_offset);
}

base::Optional<double> time_range;
Expand Down Expand Up @@ -238,8 +205,11 @@ const std::vector<double> ScrollTimeline::GetResolvedScrollOffsets() const {

// Resolves scroll offsets and stores them into resolved_offsets argument.
// Returns true if the offsets are resolved.
// https://drafts.csswg.org/scroll-animations-1/#effective-scroll-offsets-algorithm
bool ScrollTimeline::ResolveScrollOffsets(
WTF::Vector<double>& resolved_offsets) const {
// 1. Let effective scroll offsets be an empty list of effective scroll
// offsets.
DCHECK(resolved_offsets.IsEmpty());
DCHECK(ComputeIsActive());
LayoutBox* layout_box = resolved_scroll_source_->GetLayoutBox();
Expand All @@ -251,26 +221,59 @@ bool ScrollTimeline::ResolveScrollOffsets(

auto orientation = ToPhysicalScrollOrientation(orientation_, *layout_box);

// 2. Let first offset be true.
// first_offset signifies weather min or max scroll offset is pushed to
// effective scroll offsets.
bool first_offset = true;

// 3. If scrollOffsets is empty
if (scroll_offsets_.size() == 0) {
// Start and end offsets resolve to 'auto'.
// 3.1 Run the procedure to resolve a scroll timeline offset for auto with
// the is first flag set to first offset and add the resulted value into
// effective scroll offsets.
resolved_offsets.push_back(0);
// 3.2 Set first offset to false.
// 3.3 Run the procedure to resolve a scroll timeline offset for auto with
// the is first flag set to first offset and add the resulted value into
// effective scroll offsets.
resolved_offsets.push_back(max_offset);
return true;
}
// Single entry offset in scrollOffsets is considered as 'end'.
if (scroll_offsets_.size() == 1)
// 4. If scrollOffsets has exactly one element
if (scroll_offsets_.size() == 1) {
// 4.1 Run the procedure to resolve a scroll timeline offset for auto with
// the is first flag set to first offset and add the resulted value into
// effective scroll offsets.
resolved_offsets.push_back(0);
// 4.2 Set first offset to false.
first_offset = false;
}

// 5. For each scroll offset in the list of scrollOffsets, perform the
// following steps:
for (auto& offset : scroll_offsets_) {
auto resolved_offset = offset->ResolveOffset(
resolved_scroll_source_, orientation, max_offset, max_offset);
// 5.1 Let effective offset be the result of applying the procedure to
// resolve a scroll timeline offset for scroll offset with the is first flag
// set to first offset.
auto resolved_offset =
offset->ResolveOffset(resolved_scroll_source_, orientation, max_offset,
first_offset ? 0 : max_offset);
if (!resolved_offset) {
// Empty resolved offset if any of the offsets cannot be resolved.
// 5.2 If effective offset is null, the effective scroll offsets is empty
// and abort the remaining steps.
resolved_offsets.clear();
return false;
}
// 5.3 Add effective offset into effective scroll offsets.
resolved_offsets.push_back(resolved_offset.value());

// 5.4 Set first offset to false.
first_offset = false;
}
DCHECK_GE(resolved_offsets.size(), 2u);
// 6. Return effective scroll offsets.
return true;
}

Expand Down Expand Up @@ -323,6 +326,7 @@ void ScrollTimeline::duration(CSSNumberish& duration) {
}
}

// https://drafts.csswg.org/scroll-animations-1/#current-time-algorithm
ScrollTimeline::TimelineState ScrollTimeline::ComputeTimelineState() const {
// 1. If scroll timeline is inactive, return an unresolved time value.
// https://github.com/WICG/scroll-animations/issues/31
Expand Down Expand Up @@ -356,15 +360,20 @@ ScrollTimeline::TimelineState ScrollTimeline::ComputeTimelineState() const {
// to add a special case here. See
// https://github.com/WICG/scroll-animations/issues/20

// 3. If current scroll offset is less than startScrollOffset:
// 3. The current time is the result corresponding to the first matching
// condition from below:
// 3.1 If current scroll offset is less than effective start offset:
// The current time is 0.
if (current_offset < start_offset) {
return {TimelinePhase::kBefore, base::TimeDelta(), resolved_offsets};
}

double duration =
time_range_ ? time_range_.value() : kScrollTimelineDurationMs;

// 4. If current scroll offset is greater than or equal to endScrollOffset:
// 3.2 If current scroll offset is greater than or equal to effective end
// offset:
// The current time is the effective time range.
if (current_offset >= end_offset) {
// If end_offset is greater than or equal to the maximum scroll offset of
// scrollSource in orientation then return active phase, otherwise return
Expand All @@ -375,9 +384,12 @@ ScrollTimeline::TimelineState ScrollTimeline::ComputeTimelineState() const {
resolved_offsets};
}

// 5. Return the result of evaluating the following expression:
// ((current scroll offset - startScrollOffset) /
// (endScrollOffset - startScrollOffset)) * effective time range
// 3.3 Otherwise,
// 3.3.1 Let progress be a result of applying calculate scroll timeline
// progress procedure for current scroll offset.
// 3.3.2 The current time is the result of evaluating the following
// expression:
// progress × effective time range
base::Optional<base::TimeDelta> calculated_current_time =
base::TimeDelta::FromMillisecondsD(scroll_timeline_util::ComputeProgress(
current_offset, resolved_offsets) *
Expand Down Expand Up @@ -447,27 +459,6 @@ String ScrollTimeline::orientation() {
}
}

// TODO(crbug.com/1094014): scrollOffsets will replace start and end
// offsets once spec decision on multiple scroll offsets is finalized.
// https://github.com/w3c/csswg-drafts/issues/4912
void ScrollTimeline::startScrollOffset(ScrollTimelineOffsetValue& out) const {
if (StartScrollOffset()) {
out = StartScrollOffset()->ToScrollTimelineOffsetValue();
} else {
ScrollTimelineOffset scrollOffset;
out = scrollOffset.ToScrollTimelineOffsetValue();
}
}

void ScrollTimeline::endScrollOffset(ScrollTimelineOffsetValue& out) const {
if (EndScrollOffset()) {
out = EndScrollOffset()->ToScrollTimelineOffsetValue();
} else {
ScrollTimelineOffset scrollOffset;
out = scrollOffset.ToScrollTimelineOffsetValue();
}
}

const HeapVector<ScrollTimelineOffsetValue> ScrollTimeline::scrollOffsets()
const {
HeapVector<ScrollTimelineOffsetValue> scroll_offsets;
Expand Down
6 changes: 1 addition & 5 deletions blink/renderer/core/animation/scroll_timeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,6 @@ class CORE_EXPORT ScrollTimeline : public AnimationTimeline {
// IDL API implementation.
Element* scrollSource() const;
String orientation();
// TODO(crbug.com/1094014): scrollOffsets will replace start and end
// offsets once spec decision on multiple scroll offsets is finalized.
// https://github.com/w3c/csswg-drafts/issues/4912
void startScrollOffset(ScrollTimelineOffsetValue& result) const;
void endScrollOffset(ScrollTimelineOffsetValue& result) const;
const HeapVector<ScrollTimelineOffsetValue> scrollOffsets() const;

void currentTime(CSSNumberish&) override;
Expand Down Expand Up @@ -130,6 +125,7 @@ class CORE_EXPORT ScrollTimeline : public AnimationTimeline {

private:
FRIEND_TEST_ALL_PREFIXES(ScrollTimelineTest, MultipleScrollOffsetsClamping);
FRIEND_TEST_ALL_PREFIXES(ScrollTimelineTest, ResolveScrollOffsets);
// https://wicg.github.io/scroll-animations/#avoiding-cycles
// Snapshots scroll timeline current time and phase.
// Called once per animation frame.
Expand Down
2 changes: 0 additions & 2 deletions blink/renderer/core/animation/scroll_timeline.idl
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ typedef (ScrollTimelineContainerBasedOffset or ScrollTimelineElementBasedOffset)
[CallWith=Document, RaisesException, MeasureAs=ScrollTimelineConstructor] constructor(optional ScrollTimelineOptions options = {});
readonly attribute Element? scrollSource;
readonly attribute ScrollDirection orientation;
readonly attribute ScrollTimelineOffset startScrollOffset;
readonly attribute ScrollTimelineOffset endScrollOffset;
readonly attribute FrozenArray<ScrollTimelineOffset> scrollOffsets;
readonly attribute (double or ScrollTimelineAutoKeyword) timeRange;
};
2 changes: 0 additions & 2 deletions blink/renderer/core/animation/scroll_timeline_options.idl
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ enum ScrollTimelineAutoKeyword { "auto" };
dictionary ScrollTimelineOptions {
Element? scrollSource;
ScrollDirection orientation = "block";
ScrollTimelineOffset startScrollOffset = "auto";
ScrollTimelineOffset endScrollOffset = "auto";
sequence<ScrollTimelineOffset> scrollOffsets = [];
(double or ScrollTimelineAutoKeyword) timeRange = "auto";
};
85 changes: 79 additions & 6 deletions blink/renderer/core/animation/scroll_timeline_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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>(
Expand Down Expand Up @@ -149,8 +156,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);

Expand Down Expand Up @@ -218,8 +225,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);

Expand Down Expand Up @@ -271,8 +278,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);

Expand Down Expand Up @@ -786,6 +793,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>
Expand Down
4 changes: 2 additions & 2 deletions blink/renderer/core/animation/scroll_timeline_util_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ TEST_F(ScrollTimelineUtilTest, ToCompositorScrollTimeline) {
options->setTimeRange(
DoubleOrScrollTimelineAutoKeyword::FromDouble(time_range));
options->setOrientation("block");
options->setStartScrollOffset(OffsetFromString(GetDocument(), "50px"));
options->setEndScrollOffset(OffsetFromString(GetDocument(), "auto"));
options->setScrollOffsets({OffsetFromString(GetDocument(), "50px"),
OffsetFromString(GetDocument(), "auto")});
ScrollTimeline* timeline =
ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);

Expand Down
Loading

0 comments on commit 1c4cc8d

Please sign in to comment.