Skip to content

Commit

Permalink
Handle style dependent animation ranges
Browse files Browse the repository at this point in the history
Bug: 1448638

(cherry picked from commit ff09807)

Change-Id: Ifbe2ce74a65dc6dadfed841c00e000c6e499797f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4567310
Commit-Queue: Kevin Ellis <kevers@chromium.org>
Reviewed-by: Robert Flack <flackr@chromium.org>
Cr-Original-Commit-Position: refs/heads/main@{#1150297}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4572940
Reviewed-by: Eugene Girard <girard@chromium.org>
Reviewed-by: Krishna Govind <govind@chromium.org>
Commit-Queue: Krishna Govind <govind@chromium.org>
Owners-Override: Krishna Govind <govind@chromium.org>
Cr-Commit-Position: refs/branch-heads/5790@{#154}
Cr-Branched-From: 1d71a33-refs/heads/main@{#1148114}
  • Loading branch information
kevers-google authored and Chromium LUCI CQ committed May 30, 2023
1 parent 4617f11 commit edb13c7
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 42 deletions.
64 changes: 60 additions & 4 deletions third_party/blink/renderer/core/animation/animation.cc
Expand Up @@ -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<KeyframeEffect>(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:
Expand All @@ -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();
}

Expand Down Expand Up @@ -2451,6 +2470,42 @@ bool Animation::OnValidateSnapshot(bool snapshot_changed) {
return !needs_update;
}

void Animation::SetRangeStartInternal(
const absl::optional<TimelineOffset>& 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<TimelineOffset>& 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<TimelineOffset>& range_start,
const absl::optional<TimelineOffset>& 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<ScrollSnapshotTimeline>(timeline_.Get())) {
Expand All @@ -2459,8 +2514,7 @@ void Animation::OnRangeUpdate() {

// Force recalculation of the intrinsic iteration duration.
InvalidateNormalizedTiming();

if (!auto_align_start_time_) {
if (PendingInternal()) {
return;
}

Expand Down Expand Up @@ -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);
}
Expand Down
28 changes: 6 additions & 22 deletions third_party/blink/renderer/core/animation/animation.h
Expand Up @@ -230,33 +230,14 @@ class CORE_EXPORT Animation : public EventTargetWithInlineData,
const absl::optional<TimelineOffset>& GetRangeEndInternal() const {
return range_end_;
}
void SetRangeStartInternal(
const absl::optional<TimelineOffset>& range_start) {
auto_align_start_time_ = true;
if (range_start_ != range_start) {
range_start_ = range_start;
OnRangeUpdate();
}
}
void SetRangeEndInternal(const absl::optional<TimelineOffset>& range_end) {
auto_align_start_time_ = true;
if (range_end_ != range_end) {
range_end_ = range_end;
OnRangeUpdate();
}
}
void SetRangeStartInternal(const absl::optional<TimelineOffset>& range_start);
void SetRangeEndInternal(const absl::optional<TimelineOffset>& 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<TimelineOffset>& range_start,
const absl::optional<TimelineOffset>& range_end) {
if (range_start_ != range_start || range_end_ != range_end) {
range_start_ = range_start;
range_end_ = range_end;
OnRangeUpdate();
}
}
const absl::optional<TimelineOffset>& 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
Expand Down Expand Up @@ -540,6 +521,9 @@ class CORE_EXPORT Animation : public EventTargetWithInlineData,
absl::optional<TimelineOffset> range_start_;
absl::optional<TimelineOffset> range_end_;

Member<CSSValue> style_dependent_range_start_;
Member<CSSValue> style_dependent_range_end_;

ReplaceState replace_state_;

// Testing flags.
Expand Down
92 changes: 78 additions & 14 deletions third_party/blink/renderer/core/animation/timeline_offset.cc
Expand Up @@ -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 <length-percent> pair");
}
Expand Down Expand Up @@ -58,16 +58,23 @@ String TimelineOffset::TimelineRangeNameToString(
}

String TimelineOffset::ToString() const {
if (name == NamedRange::kNone) {
return "normal";
}

CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*MakeGarbageCollected<CSSIdentifierValue>(name));
if (name != NamedRange::kNone) {
list->Append(*MakeGarbageCollected<CSSIdentifierValue>(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> TimelineOffset::Create(
Element* element,
Expand All @@ -94,7 +101,7 @@ absl::optional<TimelineOffset> TimelineOffset::Create(
/* default_offset_percent */ default_percent);

if (!value || !range.AtEnd()) {
ThrowExcpetionForInvalidTimelineOffset(exception_state);
ThrowExceptionForInvalidTimelineOffset(exception_state);
return absl::nullopt;
}

Expand All @@ -105,21 +112,28 @@ absl::optional<TimelineOffset> TimelineOffset::Create(

const auto& list = To<CSSValueList>(*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<String> style_dependent_offset_str;
if (list.Item(0).IsIdentifierValue()) {
range_name = To<CSSIdentifierValue>(list.Item(0)).ConvertTo<NamedRange>();
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 */
Expand All @@ -138,6 +152,7 @@ absl::optional<TimelineOffset> TimelineOffset::Create(
value->hasRangeName() ? value->rangeName().AsEnum() : NamedRange::kNone;

Length parsed_offset;
absl::optional<String> style_dependent_offset_str;
if (value->hasOffset()) {
CSSNumericValue* offset = value->offset();
const CSSPrimitiveValue* css_value =
Expand All @@ -158,16 +173,44 @@ absl::optional<TimelineOffset> 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<CSSPrimitiveValue>(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<CSSPrimitiveValue>(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.
Expand All @@ -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(),
Expand All @@ -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<CSSParserToken, 32> 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
21 changes: 19 additions & 2 deletions third_party/blink/renderer/core/animation/timeline_offset.h
Expand Up @@ -11,20 +11,30 @@

namespace blink {

class Document;
class Element;
class CSSValue;

struct TimelineOffset {
using NamedRange = V8TimelineRange::Enum;

TimelineOffset() = default;
TimelineOffset(NamedRange name, Length offset) : name(name), offset(offset) {}
TimelineOffset(NamedRange name,
Length offset,
absl::optional<String> 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<String> 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 {
Expand All @@ -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;
Expand Down

0 comments on commit edb13c7

Please sign in to comment.