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