1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/animation/scroll_timeline.h"
6
7 #include <tuple>
8
9 #include "base/optional.h"
10 #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_timeline_options.h"
11 #include "third_party/blink/renderer/core/animation/scroll_timeline_offset.h"
12 #include "third_party/blink/renderer/core/animation/scroll_timeline_util.h"
13 #include "third_party/blink/renderer/core/css/css_to_length_conversion_data.h"
14 #include "third_party/blink/renderer/core/css/cssom/css_unit_values.h"
15 #include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
16 #include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
17 #include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
18 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
19 #include "third_party/blink/renderer/core/layout/layout_box.h"
20 #include "third_party/blink/renderer/core/layout/layout_view.h"
21 #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
22 #include "third_party/blink/renderer/core/paint/paint_layer.h"
23 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
24 #include "third_party/blink/renderer/core/scroll/scroll_types.h"
25 #include "third_party/blink/renderer/platform/geometry/length_functions.h"
26
27 namespace blink {
28
29 namespace {
30
31 constexpr double kScrollTimelineDuration = 100.0;
32 // Animation times are tracked as TimeDeltas which are stored internally as an
33 // integer number of microseconds. Multiplying by 1000 converts this into a
34 // value equivalent to Milliseconds.
35 constexpr double kScrollTimelineDurationMs = kScrollTimelineDuration * 1000.0;
36
37 using ScrollTimelineSet =
38 HeapHashMap<WeakMember<Node>,
39 Member<HeapHashSet<WeakMember<ScrollTimeline>>>>;
GetScrollTimelineSet()40 ScrollTimelineSet& GetScrollTimelineSet() {
41 DEFINE_STATIC_LOCAL(Persistent<ScrollTimelineSet>, set,
42 (MakeGarbageCollected<ScrollTimelineSet>()));
43 return *set;
44 }
45
46 using ActiveScrollTimelineSet = HeapHashCountedSet<WeakMember<Node>>;
GetActiveScrollTimelineSet()47 ActiveScrollTimelineSet& GetActiveScrollTimelineSet() {
48 DEFINE_STATIC_LOCAL(Persistent<ActiveScrollTimelineSet>, set,
49 (MakeGarbageCollected<ActiveScrollTimelineSet>()));
50 return *set;
51 }
52
StringToScrollDirection(String scroll_direction,ScrollTimeline::ScrollDirection & result)53 bool StringToScrollDirection(String scroll_direction,
54 ScrollTimeline::ScrollDirection& result) {
55 if (scroll_direction == "block") {
56 result = ScrollTimeline::Block;
57 return true;
58 }
59 if (scroll_direction == "inline") {
60 result = ScrollTimeline::Inline;
61 return true;
62 }
63 if (scroll_direction == "horizontal") {
64 result = ScrollTimeline::Horizontal;
65 return true;
66 }
67 if (scroll_direction == "vertical") {
68 result = ScrollTimeline::Vertical;
69 return true;
70 }
71 return false;
72 }
73
ToPhysicalScrollOrientation(ScrollTimeline::ScrollDirection direction,const LayoutBox & source_box)74 ScrollOrientation ToPhysicalScrollOrientation(
75 ScrollTimeline::ScrollDirection direction,
76 const LayoutBox& source_box) {
77 bool is_horizontal = source_box.IsHorizontalWritingMode();
78 switch (direction) {
79 case ScrollTimeline::Block:
80 return is_horizontal ? kVerticalScroll : kHorizontalScroll;
81 case ScrollTimeline::Inline:
82 return is_horizontal ? kHorizontalScroll : kVerticalScroll;
83 case ScrollTimeline::Horizontal:
84 return kHorizontalScroll;
85 case ScrollTimeline::Vertical:
86 return kVerticalScroll;
87 }
88 }
89
90 // Note that the resolution process may trigger document lifecycle to clean
91 // style and layout.
ResolveScrollSource(Element * scroll_source)92 Node* ResolveScrollSource(Element* scroll_source) {
93 // When in quirks mode we need the style to be clean, so we don't use
94 // |ScrollingElementNoLayout|.
95 if (scroll_source &&
96 scroll_source == scroll_source->GetDocument().scrollingElement()) {
97 return &scroll_source->GetDocument();
98 }
99 return scroll_source;
100 }
101 } // namespace
102
Create(Document & document,ScrollTimelineOptions * options,ExceptionState & exception_state)103 ScrollTimeline* ScrollTimeline::Create(Document& document,
104 ScrollTimelineOptions* options,
105 ExceptionState& exception_state) {
106 Element* scroll_source = options->hasScrollSource()
107 ? options->scrollSource()
108 : document.scrollingElement();
109
110 ScrollDirection orientation;
111 if (!StringToScrollDirection(options->orientation(), orientation)) {
112 exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
113 "Invalid orientation");
114 return nullptr;
115 }
116
117 ScrollTimelineOffset* start_scroll_offset =
118 ScrollTimelineOffset::Create(options->startScrollOffset());
119 if (!start_scroll_offset) {
120 exception_state.ThrowTypeError("Invalid start offset.");
121 return nullptr;
122 }
123
124 ScrollTimelineOffset* end_scroll_offset =
125 ScrollTimelineOffset::Create(options->endScrollOffset());
126 if (!end_scroll_offset) {
127 exception_state.ThrowTypeError("Invalid end offset");
128 return nullptr;
129 }
130
131 // TODO(crbug.com/1094014): Either scroll offsets or start/end offsets can
132 // be specified.
133 if (!options->scrollOffsets().IsEmpty() &&
134 (!start_scroll_offset->IsDefaultValue() ||
135 !end_scroll_offset->IsDefaultValue())) {
136 exception_state.ThrowTypeError(
137 "Either scrollOffsets or start/end offsets can be specified.");
138 return nullptr;
139 }
140
141 HeapVector<Member<ScrollTimelineOffset>>* scroll_offsets =
142 MakeGarbageCollected<HeapVector<Member<ScrollTimelineOffset>>>();
143 if (options->scrollOffsets().IsEmpty()) {
144 // TODO(crbug.com/1094014): scroll_offsets will replace start and end
145 // offsets once spec decision on multiple scroll offsets is finalized.
146 // https://github.com/w3c/csswg-drafts/issues/4912
147 if (!start_scroll_offset->IsDefaultValue())
148 scroll_offsets->push_back(start_scroll_offset);
149 if (!end_scroll_offset->IsDefaultValue() ||
150 !start_scroll_offset->IsDefaultValue())
151 scroll_offsets->push_back(end_scroll_offset);
152 } else {
153 for (auto& offset : options->scrollOffsets()) {
154 ScrollTimelineOffset* scroll_offset =
155 ScrollTimelineOffset::Create(offset);
156 if (!scroll_offset) {
157 exception_state.ThrowTypeError("Invalid scroll offset");
158 return nullptr;
159 }
160 if (scroll_offset->IsDefaultValue() &&
161 (options->scrollOffsets().size() == 1 ||
162 (scroll_offsets->size() + 1) < options->scrollOffsets().size())) {
163 exception_state.ThrowTypeError(
164 "Invalid scrollOffsets: 'auto' can only be set as an end "
165 "offset when start offset presents.");
166 return nullptr;
167 }
168 scroll_offsets->push_back(scroll_offset);
169 }
170 }
171
172 base::Optional<double> time_range;
173 if (options->timeRange().IsDouble()) {
174 time_range = base::make_optional(options->timeRange().GetAsDouble());
175 }
176
177 return MakeGarbageCollected<ScrollTimeline>(
178 &document, scroll_source, orientation, scroll_offsets, time_range);
179 }
180
ScrollTimeline(Document * document,Element * scroll_source,ScrollDirection orientation,HeapVector<Member<ScrollTimelineOffset>> * scroll_offsets,base::Optional<double> time_range)181 ScrollTimeline::ScrollTimeline(
182 Document* document,
183 Element* scroll_source,
184 ScrollDirection orientation,
185 HeapVector<Member<ScrollTimelineOffset>>* scroll_offsets,
186 base::Optional<double> time_range)
187 : AnimationTimeline(document),
188 scroll_source_(scroll_source),
189 resolved_scroll_source_(ResolveScrollSource(scroll_source_)),
190 orientation_(orientation),
191 scroll_offsets_(scroll_offsets),
192 time_range_(time_range) {
193 DCHECK(scroll_offsets_);
194 if (resolved_scroll_source_) {
195 ScrollTimelineSet& set = GetScrollTimelineSet();
196 if (!set.Contains(resolved_scroll_source_)) {
197 set.insert(
198 resolved_scroll_source_,
199 MakeGarbageCollected<HeapHashSet<WeakMember<ScrollTimeline>>>());
200 }
201 auto it = set.find(resolved_scroll_source_);
202 it->value->insert(this);
203 }
204 SnapshotState();
205 }
206
IsActive() const207 bool ScrollTimeline::IsActive() const {
208 return timeline_state_snapshotted_.phase != TimelinePhase::kInactive;
209 }
210
Invalidate()211 void ScrollTimeline::Invalidate() {
212 ScheduleNextServiceInternal(/* time_check = */ false);
213 }
214
ComputeIsActive() const215 bool ScrollTimeline::ComputeIsActive() const {
216 LayoutBox* layout_box = resolved_scroll_source_
217 ? resolved_scroll_source_->GetLayoutBox()
218 : nullptr;
219 return layout_box && layout_box->IsScrollContainer();
220 }
221
StartScrollOffset() const222 ScrollTimelineOffset* ScrollTimeline::StartScrollOffset() const {
223 // Single entry offset in scrollOffsets is considered as 'end'. Thus,
224 // resolving start offset only if there is at least 2 offsets.
225 return scroll_offsets_ && scroll_offsets_->size() >= 2
226 ? scroll_offsets_->at(0)
227 : nullptr;
228 }
EndScrollOffset() const229 ScrollTimelineOffset* ScrollTimeline::EndScrollOffset() const {
230 // End offset is always the last offset in scrollOffsets if exists.
231 return scroll_offsets_ && scroll_offsets_->size() >= 1
232 ? scroll_offsets_->at(scroll_offsets_->size() - 1)
233 : nullptr;
234 }
235
GetResolvedScrollOffsets() const236 const std::vector<double> ScrollTimeline::GetResolvedScrollOffsets() const {
237 std::vector<double> resolved_offsets;
238 for (const auto& offset : timeline_state_snapshotted_.scroll_offsets)
239 resolved_offsets.push_back(offset);
240 return resolved_offsets;
241 }
242
243 // Resolves scroll offsets and stores them into resolved_offsets argument.
244 // Returns true if the offsets are resolved.
ResolveScrollOffsets(WTF::Vector<double> & resolved_offsets) const245 bool ScrollTimeline::ResolveScrollOffsets(
246 WTF::Vector<double>& resolved_offsets) const {
247 DCHECK(resolved_offsets.IsEmpty());
248 DCHECK(ComputeIsActive());
249 LayoutBox* layout_box = resolved_scroll_source_->GetLayoutBox();
250 DCHECK(layout_box);
251
252 double current_offset;
253 double max_offset;
254 GetCurrentAndMaxOffset(layout_box, current_offset, max_offset);
255
256 auto orientation = ToPhysicalScrollOrientation(orientation_, *layout_box);
257
258 if (scroll_offsets_->size() == 0) {
259 // Start and end offsets resolve to 'auto'.
260 resolved_offsets.push_back(0);
261 resolved_offsets.push_back(max_offset);
262 return true;
263 }
264 // Single entry offset in scrollOffsets is considered as 'end'.
265 if (scroll_offsets_->size() == 1)
266 resolved_offsets.push_back(0);
267 for (auto& offset : *scroll_offsets_) {
268 auto resolved_offset = offset->ResolveOffset(
269 resolved_scroll_source_, orientation, max_offset, max_offset);
270 if (!resolved_offset) {
271 // Empty resolved offset if any of the offsets cannot be resolved.
272 resolved_offsets.clear();
273 return false;
274 }
275 resolved_offsets.push_back(resolved_offset.value());
276 }
277 // TODO(crbug.com/1094014): Implement clamping for overlapping offsets.
278 DCHECK_GE(resolved_offsets.size(), 2u);
279 return true;
280 }
281
CurrentPhaseAndTime()282 AnimationTimeline::PhaseAndTime ScrollTimeline::CurrentPhaseAndTime() {
283 return {timeline_state_snapshotted_.phase,
284 timeline_state_snapshotted_.current_time};
285 }
286
ScrollOffsetsEqual(const HeapVector<Member<ScrollTimelineOffset>> & other) const287 bool ScrollTimeline::ScrollOffsetsEqual(
288 const HeapVector<Member<ScrollTimelineOffset>>& other) const {
289 DCHECK(scroll_offsets_);
290 if (scroll_offsets_->size() != other.size())
291 return false;
292 size_t size = scroll_offsets_->size();
293 for (size_t i = 0; i < size; ++i) {
294 if (!DataEquivalent(scroll_offsets_->at(i), other.at(i)))
295 return false;
296 }
297 return true;
298 }
299
currentTime(CSSNumberish & currentTime)300 void ScrollTimeline::currentTime(CSSNumberish& currentTime) {
301 // time returns either in milliseconds or a 0 to 100 value representing the
302 // progress of the timeline
303 auto current_time = timeline_state_snapshotted_.current_time;
304
305 // TODO(crbug.com/1140602): Support progress based animations
306 // We are currently abusing the intended use of the "auto" keyword. We are
307 // using it here as a signal to use progress based timeline instead of having
308 // a range based current time.
309 // We are doing this maintain backwards compatibility with existing tests.
310 if (time_range_) {
311 // not using progress based, return time as double
312 currentTime =
313 current_time ? CSSNumberish::FromDouble(current_time->InMillisecondsF())
314 : CSSNumberish();
315 } else {
316 currentTime = current_time
317 ? CSSNumberish::FromCSSNumericValue(
318 CSSUnitValues::percent(current_time->InSecondsF()))
319 : CSSNumberish();
320 }
321 }
322
duration(CSSNumberish & duration)323 void ScrollTimeline::duration(CSSNumberish& duration) {
324 if (time_range_) {
325 duration = CSSNumberish::FromDouble(time_range_.value());
326 } else {
327 duration = CSSNumberish::FromCSSNumericValue(
328 CSSUnitValues::percent(kScrollTimelineDuration));
329 }
330 }
331
ComputeTimelineState() const332 ScrollTimeline::TimelineState ScrollTimeline::ComputeTimelineState() const {
333 // 1. If scroll timeline is inactive, return an unresolved time value.
334 // https://github.com/WICG/scroll-animations/issues/31
335 // https://wicg.github.io/scroll-animations/#current-time-algorithm
336 WTF::Vector<double> resolved_offsets;
337 if (!ComputeIsActive()) {
338 return {TimelinePhase::kInactive, /*current_time*/ base::nullopt,
339 resolved_offsets};
340 }
341 LayoutBox* layout_box = resolved_scroll_source_->GetLayoutBox();
342 // 2. Otherwise, let current scroll offset be the current scroll offset of
343 // scrollSource in the direction specified by orientation.
344
345 double current_offset;
346 double max_offset;
347 GetCurrentAndMaxOffset(layout_box, current_offset, max_offset);
348
349 bool resolved = ResolveScrollOffsets(resolved_offsets);
350
351 if (!resolved) {
352 DCHECK(resolved_offsets.IsEmpty());
353 return {TimelinePhase::kInactive, /*current_time*/ base::nullopt,
354 resolved_offsets};
355 }
356
357 double start_offset = resolved_offsets[0];
358 double end_offset = resolved_offsets[resolved_offsets.size() - 1];
359
360 // TODO(crbug.com/1060384): Once the spec has been updated to state what the
361 // expected result is when startScrollOffset >= endScrollOffset, we might need
362 // to add a special case here. See
363 // https://github.com/WICG/scroll-animations/issues/20
364
365 // 3. If current scroll offset is less than startScrollOffset:
366 if (current_offset < start_offset) {
367 return {TimelinePhase::kBefore, base::TimeDelta(), resolved_offsets};
368 }
369
370 double duration =
371 time_range_ ? time_range_.value() : kScrollTimelineDurationMs;
372
373 // 4. If current scroll offset is greater than or equal to endScrollOffset:
374 if (current_offset >= end_offset) {
375 // If end_offset is greater than or equal to the maximum scroll offset of
376 // scrollSource in orientation then return active phase, otherwise return
377 // after phase.
378 TimelinePhase phase = end_offset >= max_offset ? TimelinePhase::kActive
379 : TimelinePhase::kAfter;
380 return {phase, base::TimeDelta::FromMillisecondsD(duration),
381 resolved_offsets};
382 }
383
384 // 5. Return the result of evaluating the following expression:
385 // ((current scroll offset - startScrollOffset) /
386 // (endScrollOffset - startScrollOffset)) * effective time range
387 base::Optional<base::TimeDelta> calculated_current_time =
388 base::TimeDelta::FromMillisecondsD(scroll_timeline_util::ComputeProgress(
389 current_offset, resolved_offsets) *
390 duration);
391 return {TimelinePhase::kActive, calculated_current_time, resolved_offsets};
392 }
393
394 // Scroll-linked animations are initialized with the start time of zero.
395 base::Optional<base::TimeDelta>
InitialStartTimeForAnimations()396 ScrollTimeline::InitialStartTimeForAnimations() {
397 return base::TimeDelta();
398 }
399
ServiceAnimations(TimingUpdateReason reason)400 void ScrollTimeline::ServiceAnimations(TimingUpdateReason reason) {
401 // Snapshot timeline state once at top of animation frame.
402 if (reason == kTimingUpdateForAnimationFrame)
403 SnapshotState();
404 // When scroll timeline goes from inactive to active the animations may need
405 // to be started and possibly composited.
406 bool was_active =
407 last_current_phase_and_time_ &&
408 last_current_phase_and_time_.value().phase == TimelinePhase::kActive;
409 if (!was_active && IsActive())
410 MarkAnimationsCompositorPending();
411
412 AnimationTimeline::ServiceAnimations(reason);
413 }
414
ScheduleNextServiceInternal(bool time_check)415 void ScrollTimeline::ScheduleNextServiceInternal(bool time_check) {
416 if (AnimationsNeedingUpdateCount() == 0)
417 return;
418
419 if (time_check) {
420 auto state = ComputeTimelineState();
421 PhaseAndTime current_phase_and_time{state.phase, state.current_time};
422 if (current_phase_and_time == last_current_phase_and_time_)
423 return;
424 }
425 ScheduleServiceOnNextFrame();
426 }
427
ScheduleNextService()428 void ScrollTimeline::ScheduleNextService() {
429 ScheduleNextServiceInternal(/* time_check = */ true);
430 }
431
SnapshotState()432 void ScrollTimeline::SnapshotState() {
433 timeline_state_snapshotted_ = ComputeTimelineState();
434 }
435
scrollSource() const436 Element* ScrollTimeline::scrollSource() const {
437 return scroll_source_.Get();
438 }
439
orientation()440 String ScrollTimeline::orientation() {
441 switch (orientation_) {
442 case Block:
443 return "block";
444 case Inline:
445 return "inline";
446 case Horizontal:
447 return "horizontal";
448 case Vertical:
449 return "vertical";
450 default:
451 NOTREACHED();
452 return "";
453 }
454 }
455
456 // TODO(crbug.com/1094014): scrollOffsets will replace start and end
457 // offsets once spec decision on multiple scroll offsets is finalized.
458 // https://github.com/w3c/csswg-drafts/issues/4912
startScrollOffset(ScrollTimelineOffsetValue & out) const459 void ScrollTimeline::startScrollOffset(ScrollTimelineOffsetValue& out) const {
460 if (StartScrollOffset()) {
461 out = StartScrollOffset()->ToScrollTimelineOffsetValue();
462 } else {
463 ScrollTimelineOffset scrollOffset;
464 out = scrollOffset.ToScrollTimelineOffsetValue();
465 }
466 }
467
endScrollOffset(ScrollTimelineOffsetValue & out) const468 void ScrollTimeline::endScrollOffset(ScrollTimelineOffsetValue& out) const {
469 if (EndScrollOffset()) {
470 out = EndScrollOffset()->ToScrollTimelineOffsetValue();
471 } else {
472 ScrollTimelineOffset scrollOffset;
473 out = scrollOffset.ToScrollTimelineOffsetValue();
474 }
475 }
476
scrollOffsets() const477 const HeapVector<ScrollTimelineOffsetValue> ScrollTimeline::scrollOffsets()
478 const {
479 HeapVector<ScrollTimelineOffsetValue> scroll_offsets;
480
481 if (!scroll_offsets_)
482 return scroll_offsets;
483
484 for (auto& offset : *scroll_offsets_) {
485 scroll_offsets.push_back(offset->ToScrollTimelineOffsetValue());
486 // 'auto' can only be the end offset.
487 DCHECK(!offset->IsDefaultValue() || scroll_offsets.size() == 2);
488 }
489 return scroll_offsets;
490 }
491
timeRange(DoubleOrScrollTimelineAutoKeyword & result)492 void ScrollTimeline::timeRange(DoubleOrScrollTimelineAutoKeyword& result) {
493 // TODO(crbug.com/1140602): Support progress based animations
494 // We are currently abusing the intended use of the "auto" keyword. We are
495 // using it here as a signal to use progress based timeline instead of having
496 // a range based current time.
497 // We are doing this maintain backwards compatibility with existing tests.
498 if (time_range_) {
499 result.SetDouble(time_range_.value());
500 } else {
501 result.SetScrollTimelineAutoKeyword("auto");
502 }
503 }
504
GetCurrentAndMaxOffset(const LayoutBox * layout_box,double & current_offset,double & max_offset) const505 void ScrollTimeline::GetCurrentAndMaxOffset(const LayoutBox* layout_box,
506 double& current_offset,
507 double& max_offset) const {
508 DCHECK(layout_box);
509 DCHECK(layout_box->GetScrollableArea());
510
511 // Depending on the writing-mode and direction, the scroll origin shifts and
512 // the scroll offset may be negative. The easiest way to deal with this is to
513 // use only the magnitude of the scroll offset, and compare it to (max_offset
514 // - min_offset).
515 PaintLayerScrollableArea* scrollable_area = layout_box->GetScrollableArea();
516
517 // Using the absolute value of the scroll offset only makes sense if either
518 // the max or min scroll offset for a given axis is 0. This should be
519 // guaranteed by the scroll origin code, but these DCHECKs ensure that.
520 DCHECK(scrollable_area->MaximumScrollOffset().Height() == 0 ||
521 scrollable_area->MinimumScrollOffset().Height() == 0);
522 DCHECK(scrollable_area->MaximumScrollOffset().Width() == 0 ||
523 scrollable_area->MinimumScrollOffset().Width() == 0);
524 ScrollOffset scroll_offset = scrollable_area->GetScrollOffset();
525 ScrollOffset scroll_dimensions = scrollable_area->MaximumScrollOffset() -
526 scrollable_area->MinimumScrollOffset();
527
528 auto physical_orientation =
529 ToPhysicalScrollOrientation(orientation_, *layout_box);
530
531 if (physical_orientation == kHorizontalScroll) {
532 current_offset = scroll_offset.Width();
533 max_offset = scroll_dimensions.Width();
534 } else {
535 current_offset = scroll_offset.Height();
536 max_offset = scroll_dimensions.Height();
537 }
538 // When using a rtl direction, current_offset grows correctly from 0 to
539 // max_offset, but is negative. Since our offsets are all just deltas along
540 // the orientation direction, we can just take the absolute current_offset and
541 // use that everywhere.
542 current_offset = std::abs(current_offset);
543 }
544
AnimationAttached(Animation * animation)545 void ScrollTimeline::AnimationAttached(Animation* animation) {
546 AnimationTimeline::AnimationAttached(animation);
547 if (resolved_scroll_source_ && scroll_animations_.IsEmpty())
548 resolved_scroll_source_->RegisterScrollTimeline(this);
549
550 scroll_animations_.insert(animation);
551 }
552
AnimationDetached(Animation * animation)553 void ScrollTimeline::AnimationDetached(Animation* animation) {
554 AnimationTimeline::AnimationDetached(animation);
555 scroll_animations_.erase(animation);
556 if (resolved_scroll_source_ && scroll_animations_.IsEmpty())
557 resolved_scroll_source_->UnregisterScrollTimeline(this);
558 }
559
WorkletAnimationAttached()560 void ScrollTimeline::WorkletAnimationAttached() {
561 if (!resolved_scroll_source_)
562 return;
563 GetActiveScrollTimelineSet().insert(resolved_scroll_source_);
564 }
565
WorkletAnimationDetached()566 void ScrollTimeline::WorkletAnimationDetached() {
567 if (!resolved_scroll_source_)
568 return;
569 GetActiveScrollTimelineSet().erase(resolved_scroll_source_);
570 }
571
Trace(Visitor * visitor) const572 void ScrollTimeline::Trace(Visitor* visitor) const {
573 visitor->Trace(scroll_animations_);
574 visitor->Trace(scroll_source_);
575 visitor->Trace(resolved_scroll_source_);
576 visitor->Trace(scroll_offsets_);
577 AnimationTimeline::Trace(visitor);
578 }
579
HasActiveScrollTimeline(Node * node)580 bool ScrollTimeline::HasActiveScrollTimeline(Node* node) {
581 ActiveScrollTimelineSet& worklet_animations_set =
582 GetActiveScrollTimelineSet();
583 auto worklet_animations_it = worklet_animations_set.find(node);
584 if (worklet_animations_it != worklet_animations_set.end() &&
585 worklet_animations_it->value > 0)
586 return true;
587
588 ScrollTimelineSet& set = GetScrollTimelineSet();
589 auto it = set.find(node);
590 if (it == set.end())
591 return false;
592
593 for (auto& timeline : *it->value) {
594 if (timeline->HasAnimations())
595 return true;
596 }
597 return false;
598 }
599
Invalidate(Node * node)600 void ScrollTimeline::Invalidate(Node* node) {
601 ScrollTimelineSet& set = GetScrollTimelineSet();
602 auto it = set.find(node);
603
604 if (it == set.end())
605 return;
606
607 for (auto& timeline : *it->value) {
608 timeline->Invalidate();
609 }
610 }
611
InvalidateEffectTargetStyle()612 void ScrollTimeline::InvalidateEffectTargetStyle() {
613 for (Animation* animation : scroll_animations_)
614 animation->InvalidateEffectTargetStyle();
615 }
616
EnsureCompositorTimeline()617 CompositorAnimationTimeline* ScrollTimeline::EnsureCompositorTimeline() {
618 if (compositor_timeline_)
619 return compositor_timeline_.get();
620
621 compositor_timeline_ = std::make_unique<CompositorAnimationTimeline>(
622 scroll_timeline_util::ToCompositorScrollTimeline(this));
623 return compositor_timeline_.get();
624 }
625
UpdateCompositorTimeline()626 void ScrollTimeline::UpdateCompositorTimeline() {
627 if (!compositor_timeline_)
628 return;
629 compositor_timeline_->UpdateCompositorTimeline(
630 scroll_timeline_util::GetCompositorScrollElementId(
631 resolved_scroll_source_),
632 GetResolvedScrollOffsets());
633 }
634
635 } // namespace blink
636