diff --git a/third_party/blink/renderer/core/animation/animation.cc b/third_party/blink/renderer/core/animation/animation.cc index c37715c1aaea5..39c3c6c1696b5 100644 --- a/third_party/blink/renderer/core/animation/animation.cc +++ b/third_party/blink/renderer/core/animation/animation.cc @@ -2385,6 +2385,24 @@ void Animation::UpdateAutoAlignedStartTime() { bool Animation::OnValidateSnapshot(bool snapshot_changed) { bool needs_update = snapshot_changed; + + // Update style-dependent range offsets. + bool range_changed = false; + if (auto* keyframe_effect = DynamicTo(effect())) { + if (keyframe_effect->target()) { + if (style_dependent_range_start_) { + DCHECK(range_start_); + range_changed |= range_start_->UpdateOffset( + keyframe_effect->target(), style_dependent_range_start_); + } + if (style_dependent_range_end_) { + DCHECK(range_end_); + range_changed |= range_end_->UpdateOffset(keyframe_effect->target(), + style_dependent_range_end_); + } + } + } + bool needs_new_start_time = false; switch (CalculateAnimationPlayState()) { case kIdle: @@ -2409,14 +2427,15 @@ bool Animation::OnValidateSnapshot(bool snapshot_changed) { needs_update = true; } needs_new_start_time = - auto_align_start_time_ && (!start_time_ || snapshot_changed); + auto_align_start_time_ && + (!start_time_ || snapshot_changed || range_changed); break; default: NOTREACHED(); } - if (snapshot_changed || needs_new_start_time) { + if (snapshot_changed || needs_new_start_time || range_changed) { InvalidateNormalizedTiming(); } @@ -2451,6 +2470,42 @@ bool Animation::OnValidateSnapshot(bool snapshot_changed) { return !needs_update; } +void Animation::SetRangeStartInternal( + const absl::optional& range_start) { + auto_align_start_time_ = true; + if (range_start_ != range_start) { + range_start_ = range_start; + if (range_start_ && range_start_->style_dependent_offset) { + style_dependent_range_start_ = TimelineOffset::ParseOffset( + GetDocument(), range_start_->style_dependent_offset.value()); + } else { + style_dependent_range_start_ = nullptr; + } + OnRangeUpdate(); + } +} + +void Animation::SetRangeEndInternal( + const absl::optional& range_end) { + auto_align_start_time_ = true; + if (range_end_ != range_end) { + range_end_ = range_end; + if (range_end_ && range_end_->style_dependent_offset) { + style_dependent_range_end_ = TimelineOffset::ParseOffset( + GetDocument(), range_end_->style_dependent_offset.value()); + } else { + style_dependent_range_end_ = nullptr; + } + OnRangeUpdate(); + } +} + +void Animation::SetRange(const absl::optional& range_start, + const absl::optional& range_end) { + SetRangeStartInternal(range_start); + SetRangeEndInternal(range_end); +} + void Animation::OnRangeUpdate() { // Change in animation range has no effect unless using a scroll-timeline. if (!IsA(timeline_.Get())) { @@ -2459,8 +2514,7 @@ void Animation::OnRangeUpdate() { // Force recalculation of the intrinsic iteration duration. InvalidateNormalizedTiming(); - - if (!auto_align_start_time_) { + if (PendingInternal()) { return; } @@ -3182,6 +3236,8 @@ void Animation::Trace(Visitor* visitor) const { visitor->Trace(finished_promise_); visitor->Trace(ready_promise_); visitor->Trace(compositor_animation_); + visitor->Trace(style_dependent_range_start_); + visitor->Trace(style_dependent_range_end_); EventTargetWithInlineData::Trace(visitor); ExecutionContextLifecycleObserver::Trace(visitor); } diff --git a/third_party/blink/renderer/core/animation/animation.h b/third_party/blink/renderer/core/animation/animation.h index 9dda0b34beb27..4348aba7ed7f2 100644 --- a/third_party/blink/renderer/core/animation/animation.h +++ b/third_party/blink/renderer/core/animation/animation.h @@ -230,33 +230,14 @@ class CORE_EXPORT Animation : public EventTargetWithInlineData, const absl::optional& GetRangeEndInternal() const { return range_end_; } - void SetRangeStartInternal( - const absl::optional& range_start) { - auto_align_start_time_ = true; - if (range_start_ != range_start) { - range_start_ = range_start; - OnRangeUpdate(); - } - } - void SetRangeEndInternal(const absl::optional& range_end) { - auto_align_start_time_ = true; - if (range_end_ != range_end) { - range_end_ = range_end; - OnRangeUpdate(); - } - } + void SetRangeStartInternal(const absl::optional& range_start); + void SetRangeEndInternal(const absl::optional& range_end); // This method is only called during style update of a CSS animation. // Preventing an endpoint from stomping a value set via the rangeStart or // rangeEnd API is performed by the caller in CSSAnimations. virtual void SetRange(const absl::optional& range_start, - const absl::optional& range_end) { - if (range_start_ != range_start || range_end_ != range_end) { - range_start_ = range_start; - range_end_ = range_end; - OnRangeUpdate(); - } - } + const absl::optional& range_end); // Called during validation of a scroll timeline to determine if a second // style and layout pass is required. During this validation step, we have an @@ -540,6 +521,9 @@ class CORE_EXPORT Animation : public EventTargetWithInlineData, absl::optional range_start_; absl::optional range_end_; + Member style_dependent_range_start_; + Member style_dependent_range_end_; + ReplaceState replace_state_; // Testing flags. diff --git a/third_party/blink/renderer/core/animation/timeline_offset.cc b/third_party/blink/renderer/core/animation/timeline_offset.cc index 32e57966a27f9..8e9dca7563ab6 100644 --- a/third_party/blink/renderer/core/animation/timeline_offset.cc +++ b/third_party/blink/renderer/core/animation/timeline_offset.cc @@ -23,7 +23,7 @@ namespace blink { namespace { -void ThrowExcpetionForInvalidTimelineOffset(ExceptionState& exception_state) { +void ThrowExceptionForInvalidTimelineOffset(ExceptionState& exception_state) { exception_state.ThrowTypeError( "Animation range must be a name pair"); } @@ -58,16 +58,23 @@ String TimelineOffset::TimelineRangeNameToString( } String TimelineOffset::ToString() const { - if (name == NamedRange::kNone) { - return "normal"; - } - CSSValueList* list = CSSValueList::CreateSpaceSeparated(); - list->Append(*MakeGarbageCollected(name)); + if (name != NamedRange::kNone) { + list->Append(*MakeGarbageCollected(name)); + } list->Append(*CSSValue::Create(offset, 1)); return list->CssText(); } +bool TimelineOffset::UpdateOffset(Element* element, CSSValue* value) { + Length new_offset = ResolveLength(element, value); + if (new_offset != offset) { + offset = new_offset; + return true; + } + return false; +} + /* static */ absl::optional TimelineOffset::Create( Element* element, @@ -94,7 +101,7 @@ absl::optional TimelineOffset::Create( /* default_offset_percent */ default_percent); if (!value || !range.AtEnd()) { - ThrowExcpetionForInvalidTimelineOffset(exception_state); + ThrowExceptionForInvalidTimelineOffset(exception_state); return absl::nullopt; } @@ -105,21 +112,28 @@ absl::optional TimelineOffset::Create( const auto& list = To(*value); - // TODO(kevers): Keep track of style dependent lengths in order - // to re-resolve on a style update. DCHECK(list.length()); NamedRange range_name = NamedRange::kNone; Length offset = Length::Percent(default_percent); + absl::optional style_dependent_offset_str; if (list.Item(0).IsIdentifierValue()) { range_name = To(list.Item(0)).ConvertTo(); if (list.length() == 2u) { - offset = ResolveLength(element, &list.Item(1)); + const CSSValue* css_offset_value = &list.Item(1); + offset = ResolveLength(element, css_offset_value); + if (IsStyleDependent(css_offset_value)) { + style_dependent_offset_str = css_offset_value->CssText(); + } } } else { - offset = ResolveLength(element, &list.Item(0)); + const CSSValue* css_offset_value = &list.Item(0); + offset = ResolveLength(element, css_offset_value); + if (IsStyleDependent(css_offset_value)) { + style_dependent_offset_str = css_offset_value->CssText(); + } } - return TimelineOffset(range_name, offset); + return TimelineOffset(range_name, offset, style_dependent_offset_str); } /* static */ @@ -138,6 +152,7 @@ absl::optional TimelineOffset::Create( value->hasRangeName() ? value->rangeName().AsEnum() : NamedRange::kNone; Length parsed_offset; + absl::optional style_dependent_offset_str; if (value->hasOffset()) { CSSNumericValue* offset = value->offset(); const CSSPrimitiveValue* css_value = @@ -158,16 +173,44 @@ absl::optional TimelineOffset::Create( } else { DCHECK(css_value->IsCalculatedPercentageWithLength()); parsed_offset = TimelineOffset::ResolveLength(element, css_value); + style_dependent_offset_str = css_value->CssText(); } } else { parsed_offset = Length::Percent(default_percent); } + return TimelineOffset(name, parsed_offset, style_dependent_offset_str); +} + +/* static */ +bool TimelineOffset::IsStyleDependent(const CSSValue* value) { + const CSSPrimitiveValue* primitive_value = + DynamicTo(value); + if (!primitive_value) { + return true; + } + + if (primitive_value->IsPercentage()) { + return false; + } + + if (primitive_value->IsPx()) { + return false; + } - return TimelineOffset(name, parsed_offset); + return true; } /* static */ Length TimelineOffset::ResolveLength(Element* element, const CSSValue* value) { + if (auto* primitive_value = DynamicTo(value)) { + if (primitive_value->IsPercentage()) { + return Length::Percent(primitive_value->GetDoubleValue()); + } + if (primitive_value->IsPx()) { + return Length::Fixed(primitive_value->GetDoubleValue()); + } + } + // Elements without the computed style don't have a layout box, // so the timeline will be inactive. // See ScrollTimeline::IsResolved. @@ -176,7 +219,6 @@ Length TimelineOffset::ResolveLength(Element* element, const CSSValue* value) { } ElementResolveContext element_resolve_context(*element); Document& document = element->GetDocument(); - // TODO(kevers): Re-resolve any value that is not px or % on a style change. CSSToLengthConversionData::Flags ignored_flags = 0; CSSToLengthConversionData length_conversion_data( element->ComputedStyleRef(), element_resolve_context.ParentStyle(), @@ -188,4 +230,26 @@ Length TimelineOffset::ResolveLength(Element* element, const CSSValue* value) { length_conversion_data); } +/* static */ +CSSValue* TimelineOffset::ParseOffset(Document* document, String css_text) { + if (!document) { + return nullptr; + } + + CSSTokenizer tokenizer(css_text); + Vector tokens = tokenizer.TokenizeToEOF(); + CSSParserTokenRange range(tokens); + range.ConsumeWhitespace(); + + CSSValue* value = css_parsing_utils::ConsumeLengthOrPercent( + range, *document->ElementSheet().Contents()->ParserContext(), + CSSPrimitiveValue::ValueRange::kAll); + + if (!range.AtEnd()) { + return nullptr; + } + + return value; +} + } // namespace blink diff --git a/third_party/blink/renderer/core/animation/timeline_offset.h b/third_party/blink/renderer/core/animation/timeline_offset.h index e9a3820508d5a..7875148e5fc89 100644 --- a/third_party/blink/renderer/core/animation/timeline_offset.h +++ b/third_party/blink/renderer/core/animation/timeline_offset.h @@ -11,6 +11,7 @@ namespace blink { +class Document; class Element; class CSSValue; @@ -18,13 +19,22 @@ struct TimelineOffset { using NamedRange = V8TimelineRange::Enum; TimelineOffset() = default; - TimelineOffset(NamedRange name, Length offset) : name(name), offset(offset) {} + TimelineOffset(NamedRange name, + Length offset, + absl::optional style_dependent_offset = absl::nullopt) + : name(name), + offset(offset), + style_dependent_offset(style_dependent_offset) {} + + bool UpdateOffset(Element* element, CSSValue* value); NamedRange name = NamedRange::kNone; Length offset = Length::Fixed(); + absl::optional style_dependent_offset; bool operator==(const TimelineOffset& other) const { - return name == other.name && offset == other.offset; + return name == other.name && offset == other.offset && + style_dependent_offset == other.style_dependent_offset; } bool operator!=(const TimelineOffset& other) const { @@ -44,6 +54,13 @@ struct TimelineOffset { double default_percent, ExceptionState& exception_state); + // A length is style dependent if using a font relative or viewport relative + // unit. We also classify all styles involving calc or var as style dependent. + // A style-dependent value needs to be re-resolved after a style change. + static bool IsStyleDependent(const CSSValue* value); + + static CSSValue* ParseOffset(Document* document, String css_text); + static Length ResolveLength(Element* element, const CSSValue* value); String ToString() const; diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-timeline-range.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-timeline-range.html new file mode 100644 index 0000000000000..cc844cb748e6f --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-timeline-range.html @@ -0,0 +1,185 @@ + +Scroll timelines and animation attachment ranges + + + + + + + +
+
+
+
+