[scroll-animations] Introduce TimelineRange
We currently persist a ViewTimeline instance (for resolving timeline offsets) on KeyframeEffectModelBase. This is incompatible with Deferred Timelines v2, because that will need to resolve timeline offsets for other kinds of timeline objects too. To prepare for that, this CL splits out the information needed to perform said timeline offset conversion into a separate object (TimelineRange), and requires all AnimationTimelines to produce such an object (though it's empty for monotonic timelines). This TimelineRange object is then propagated to the model instead of the ViewTimeline itself. This CL also changes how/when offsets are resolved, we now (re-)resolve the offsets in only these cases: (1) When the animation is attached/detached to/from a timeline. (2) When an effect if attached/detach to/from an animation. This may possibly over-resolve in some cases (e.g. when switching from a non-null timeline to a non-null timeline), but I suggest we leave that for simplicity. Note that the WPT timeline-offset-in-keyframe.html covers (1) and (2) well, so no additional test is added in this CL. Bug: 1425939, 1441013 Change-Id: I90b273c896c26f865117b93244f1e563577ec66c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4519273 Reviewed-by: Steve Kobes <skobes@chromium.org> Reviewed-by: Kevin Ellis <kevers@chromium.org> Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org> Cr-Commit-Position: refs/heads/main@{#1144251}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
797894687c
commit
02df6036d0
cc/animation
third_party/blink/renderer/core/animation
@ -36,6 +36,7 @@ class CC_ANIMATION_EXPORT ScrollTimeline : public AnimationTimeline {
|
||||
};
|
||||
|
||||
struct ScrollOffsets {
|
||||
ScrollOffsets() = default;
|
||||
ScrollOffsets(double start_offset, double end_offset) {
|
||||
start = start_offset;
|
||||
end = end_offset;
|
||||
|
@ -283,6 +283,8 @@ blink_core_sources("animation") {
|
||||
"timeline_inset.h",
|
||||
"timeline_offset.cc",
|
||||
"timeline_offset.h",
|
||||
"timeline_range.cc",
|
||||
"timeline_range.h",
|
||||
"timing.cc",
|
||||
"timing.h",
|
||||
"timing_calculations.h",
|
||||
|
@ -51,6 +51,7 @@
|
||||
#include "third_party/blink/renderer/core/animation/pending_animations.h"
|
||||
#include "third_party/blink/renderer/core/animation/scroll_timeline.h"
|
||||
#include "third_party/blink/renderer/core/animation/scroll_timeline_util.h"
|
||||
#include "third_party/blink/renderer/core/animation/timeline_range.h"
|
||||
#include "third_party/blink/renderer/core/animation/timing_calculations.h"
|
||||
#include "third_party/blink/renderer/core/css/cssom/css_unit_values.h"
|
||||
#include "third_party/blink/renderer/core/css/properties/css_property_ref.h"
|
||||
@ -947,12 +948,6 @@ void Animation::setTimeline(AnimationTimeline* timeline) {
|
||||
|
||||
reset_current_time_on_resume_ = false;
|
||||
|
||||
// Set the timeline if needed for resolving timeline offsets in kefyrames.
|
||||
if (auto* keyframe_effect = DynamicTo<KeyframeEffect>(effect())) {
|
||||
ViewTimeline* view_timeline = DynamicTo<ViewTimeline>(timeline);
|
||||
keyframe_effect->Model()->SetViewTimelineIfRequired(view_timeline);
|
||||
}
|
||||
|
||||
if (timeline && !timeline->IsMonotonicallyIncreasing()) {
|
||||
ApplyPendingPlaybackRate();
|
||||
AnimationTimeDelta boundary_time =
|
||||
@ -1151,12 +1146,20 @@ void Animation::setEffect(AnimationEffect* new_effect) {
|
||||
if (new_effect && new_effect->GetAnimation())
|
||||
new_effect->GetAnimation()->setEffect(nullptr);
|
||||
|
||||
// Clear timeline offsets for old effect.
|
||||
ResolveTimelineOffsets(TimelineRange());
|
||||
|
||||
// 6. Let the associated effect of the animation be the new effect.
|
||||
if (old_effect)
|
||||
old_effect->Detach();
|
||||
content_ = new_effect;
|
||||
if (new_effect)
|
||||
new_effect->Attach(this);
|
||||
|
||||
// Resolve timeline offsets for new effect.
|
||||
ResolveTimelineOffsets(timeline() ? timeline()->GetTimelineRange()
|
||||
: TimelineRange());
|
||||
|
||||
SetOutdated();
|
||||
|
||||
// 7. Run the procedure to update an animation’s finished state for animation
|
||||
@ -1175,9 +1178,6 @@ void Animation::setEffect(AnimationEffect* new_effect) {
|
||||
if (KeyframeEffect* keyframe_effect =
|
||||
DynamicTo<KeyframeEffect>(new_effect)) {
|
||||
keyframe_effect->SetIgnoreCSSKeyframes();
|
||||
// Set the timeline if needed for resolving timeline offsets in kefyrames.
|
||||
ViewTimeline* view_timeline = DynamicTo<ViewTimeline>(timeline());
|
||||
keyframe_effect->Model()->SetViewTimelineIfRequired(view_timeline);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2291,7 +2291,8 @@ void Animation::UpdateStartTimeForViewTimeline() {
|
||||
}
|
||||
|
||||
double relative_offset =
|
||||
boundary ? view_timeline->ToFractionalOffset(boundary.value())
|
||||
boundary ? view_timeline->GetTimelineRange().ToFractionalOffset(
|
||||
boundary.value())
|
||||
: default_offset;
|
||||
AnimationTimeDelta duration = timeline_->GetDuration().value();
|
||||
start_time_ = duration * relative_offset;
|
||||
@ -2332,6 +2333,34 @@ void Animation::OnRangeUpdate() {
|
||||
NotifyProbe();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
double ResolveAnimationRange(const absl::optional<TimelineOffset>& offset,
|
||||
const TimelineRange& timeline_range,
|
||||
double default_value) {
|
||||
if (offset.has_value()) {
|
||||
return timeline_range.ToFractionalOffset(offset.value());
|
||||
}
|
||||
if (timeline_range.IsEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Animation::ResolveTimelineOffsets(const TimelineRange& timeline_range) {
|
||||
if (auto* keyframe_effect = DynamicTo<KeyframeEffect>(effect())) {
|
||||
double range_start = ResolveAnimationRange(
|
||||
GetRangeStartInternal(), timeline_range, /* default_value */ 0);
|
||||
double range_end = ResolveAnimationRange(
|
||||
GetRangeEndInternal(), timeline_range, /* default_value */ 1);
|
||||
return keyframe_effect->Model()->ResolveTimelineOffsets(
|
||||
timeline_range, range_start, range_end);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Animation::CancelAnimationOnCompositor() {
|
||||
if (HasActiveAnimationsOnCompositor()) {
|
||||
To<KeyframeEffect>(content_.Get())
|
||||
|
@ -58,6 +58,7 @@ class AnimationTimeline;
|
||||
class Element;
|
||||
class PaintArtifactCompositor;
|
||||
class TreeScope;
|
||||
class TimelineRange;
|
||||
|
||||
class CORE_EXPORT Animation : public EventTargetWithInlineData,
|
||||
public ActiveScriptWrappable<Animation>,
|
||||
@ -244,6 +245,8 @@ class CORE_EXPORT Animation : public EventTargetWithInlineData,
|
||||
|
||||
void OnRangeUpdate();
|
||||
|
||||
bool ResolveTimelineOffsets(const TimelineRange&);
|
||||
|
||||
Document* GetDocument() const;
|
||||
|
||||
V8CSSNumberish* startTime() const;
|
||||
|
@ -24,6 +24,7 @@ AnimationTimeline::AnimationTimeline(Document* document)
|
||||
void AnimationTimeline::AnimationAttached(Animation* animation) {
|
||||
DCHECK(!animations_.Contains(animation));
|
||||
animations_.insert(animation);
|
||||
animation->ResolveTimelineOffsets(GetTimelineRange());
|
||||
}
|
||||
|
||||
void AnimationTimeline::AnimationDetached(Animation* animation) {
|
||||
@ -31,6 +32,7 @@ void AnimationTimeline::AnimationDetached(Animation* animation) {
|
||||
animations_needing_update_.erase(animation);
|
||||
if (animation->Outdated())
|
||||
outdated_animation_count_--;
|
||||
animation->ResolveTimelineOffsets(GetTimelineRange());
|
||||
}
|
||||
|
||||
bool CompareAnimations(const Member<Animation>& left,
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "base/time/time.h"
|
||||
#include "cc/animation/animation_timeline.h"
|
||||
#include "third_party/blink/renderer/core/animation/animation.h"
|
||||
#include "third_party/blink/renderer/core/animation/timeline_range.h"
|
||||
#include "third_party/blink/renderer/core/core_export.h"
|
||||
#include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
|
||||
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
|
||||
@ -86,6 +87,9 @@ class CORE_EXPORT AnimationTimeline : public ScriptWrappable {
|
||||
return AnimationTimeDelta();
|
||||
}
|
||||
|
||||
// See class TimelineRange.
|
||||
virtual TimelineRange GetTimelineRange() const { return TimelineRange(); }
|
||||
|
||||
Document* GetDocument() const { return document_; }
|
||||
virtual void AnimationAttached(Animation*);
|
||||
virtual void AnimationDetached(Animation*);
|
||||
|
@ -219,16 +219,15 @@ absl::optional<AnimationTimeDelta> CSSAnimationProxy::CalculateInheritedTime(
|
||||
// inherited_time_.
|
||||
double relative_offset;
|
||||
if (timeline->IsViewTimeline()) {
|
||||
TimelineRange timeline_range = timeline->GetTimelineRange();
|
||||
// TODO(kevers): Support animation-range for a non-view scroll-timeline.
|
||||
if (playback_rate_ >= 0) {
|
||||
relative_offset =
|
||||
range_start ? DynamicTo<ViewTimeline>(timeline)->ToFractionalOffset(
|
||||
range_start.value())
|
||||
range_start ? timeline_range.ToFractionalOffset(range_start.value())
|
||||
: 0;
|
||||
} else {
|
||||
relative_offset =
|
||||
range_end ? DynamicTo<ViewTimeline>(timeline)->ToFractionalOffset(
|
||||
range_end.value())
|
||||
range_end ? timeline_range.ToFractionalOffset(range_end.value())
|
||||
: 1;
|
||||
}
|
||||
} else {
|
||||
@ -314,13 +313,10 @@ class CSSTransitionProxy : public AnimationProxy {
|
||||
};
|
||||
|
||||
// A keyframe can have an offset as a fixed percent or as a
|
||||
// <timeline-range percent>. In the later case, we resolve as a fixed
|
||||
// percent, though this value can change as layout changes. Setting the
|
||||
// resolved offset is best effort and will be fixed or ignored later if it
|
||||
// still cannot be resolved.
|
||||
bool SetOffsets(Keyframe& keyframe,
|
||||
const KeyframeOffset& offset,
|
||||
const AnimationTimeline* timeline) {
|
||||
// <timeline-range percent>. In the later case, we store the specified
|
||||
// offset on the Keyframe, and delay the resolution that offset until later.
|
||||
// (See ResolveTimelineOffset).
|
||||
bool SetOffsets(Keyframe& keyframe, const KeyframeOffset& offset) {
|
||||
if (offset.name == TimelineOffset::NamedRange::kNone) {
|
||||
keyframe.SetOffset(offset.percent);
|
||||
return false;
|
||||
@ -328,13 +324,7 @@ bool SetOffsets(Keyframe& keyframe,
|
||||
|
||||
TimelineOffset timeline_offset(offset.name,
|
||||
Length::Percent(100 * offset.percent));
|
||||
if (timeline && timeline->IsViewTimeline() && timeline->IsResolved()) {
|
||||
double fractional_offset =
|
||||
To<ViewTimeline>(timeline)->ToFractionalOffset(timeline_offset);
|
||||
keyframe.SetOffset(fractional_offset);
|
||||
} else {
|
||||
keyframe.SetOffset(absl::nullopt);
|
||||
}
|
||||
keyframe.SetOffset(absl::nullopt);
|
||||
keyframe.SetTimelineOffset(timeline_offset);
|
||||
return true;
|
||||
}
|
||||
@ -353,7 +343,6 @@ StringKeyframeVector ProcessKeyframesRule(
|
||||
TimingFunction* default_timing_function,
|
||||
WritingMode writing_mode,
|
||||
TextDirection text_direction,
|
||||
AnimationTimeline* timeline,
|
||||
bool& has_named_range_keyframes) {
|
||||
StringKeyframeVector keyframes;
|
||||
const HeapVector<Member<StyleRuleKeyframe>>& style_keyframes =
|
||||
@ -364,7 +353,7 @@ StringKeyframeVector ProcessKeyframesRule(
|
||||
const Vector<KeyframeOffset>& offsets = style_keyframe->Keys();
|
||||
DCHECK(!offsets.empty());
|
||||
|
||||
has_named_range_keyframes |= SetOffsets(*keyframe, offsets[0], timeline);
|
||||
has_named_range_keyframes |= SetOffsets(*keyframe, offsets[0]);
|
||||
keyframe->SetEasing(default_timing_function);
|
||||
const CSSPropertyValueSet& properties = style_keyframe->Properties();
|
||||
for (unsigned j = 0; j < properties.PropertyCount(); j++) {
|
||||
@ -410,7 +399,7 @@ StringKeyframeVector ProcessKeyframesRule(
|
||||
// The last keyframe specified at a given offset is used.
|
||||
for (wtf_size_t j = 1; j < offsets.size(); ++j) {
|
||||
StringKeyframe* clone = To<StringKeyframe>(keyframe->Clone());
|
||||
has_named_range_keyframes |= SetOffsets(*clone, offsets[j], timeline);
|
||||
has_named_range_keyframes |= SetOffsets(*clone, offsets[j]);
|
||||
keyframes.push_back(clone);
|
||||
}
|
||||
}
|
||||
@ -462,8 +451,7 @@ StringKeyframeEffectModel* CreateKeyframeEffectModel(
|
||||
const AtomicString& name,
|
||||
TimingFunction* default_timing_function,
|
||||
EffectModel::CompositeOperation composite,
|
||||
size_t animation_index,
|
||||
AnimationTimeline* timeline) {
|
||||
size_t animation_index) {
|
||||
// The algorithm for constructing string keyframes for a CSS animation is
|
||||
// covered in the following spec:
|
||||
// https://drafts.csswg.org/css-animations-2/#keyframes
|
||||
@ -515,7 +503,7 @@ StringKeyframeEffectModel* CreateKeyframeEffectModel(
|
||||
keyframes = ProcessKeyframesRule(
|
||||
keyframes_rule, find_result.tree_scope, element.GetDocument(),
|
||||
parent_style, default_timing_function, writing_direction.GetWritingMode(),
|
||||
writing_direction.Direction(), timeline, has_named_range_keyframes);
|
||||
writing_direction.Direction(), has_named_range_keyframes);
|
||||
|
||||
absl::optional<double> last_offset;
|
||||
wtf_size_t merged_frame_count = 0;
|
||||
@ -654,9 +642,6 @@ StringKeyframeEffectModel* CreateKeyframeEffectModel(
|
||||
UseCounter::Count(element.GetDocument(),
|
||||
WebFeature::kCSSAnimationsStackedNeutralKeyframe);
|
||||
}
|
||||
if (has_named_range_keyframes) {
|
||||
model->SetViewTimelineIfRequired(DynamicTo<ViewTimeline>(timeline));
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
@ -1735,7 +1720,7 @@ void CSSAnimations::CalculateAnimationUpdate(
|
||||
CreateKeyframeEffectModel(
|
||||
resolver, element, animating_element, writing_direction,
|
||||
parent_style, name, keyframe_timing_function.get(),
|
||||
composite, i, timeline),
|
||||
composite, i),
|
||||
timing, animation_proxy),
|
||||
specified_timing, keyframes_rule, timeline,
|
||||
animation_data->PlayStateList(), range_start, range_end);
|
||||
@ -1757,7 +1742,7 @@ void CSSAnimations::CalculateAnimationUpdate(
|
||||
CreateKeyframeEffectModel(resolver, element, animating_element,
|
||||
writing_direction, parent_style, name,
|
||||
keyframe_timing_function.get(),
|
||||
composite, i, timeline),
|
||||
composite, i),
|
||||
timing, animation_proxy),
|
||||
specified_timing, keyframes_rule, timeline,
|
||||
animation_data->PlayStateList(), range_start, range_end);
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include "third_party/blink/renderer/bindings/core/v8/v8_timeline_range_offset.h"
|
||||
#include "third_party/blink/renderer/core/animation/effect_model.h"
|
||||
#include "third_party/blink/renderer/core/animation/invalidatable_interpolation.h"
|
||||
#include "third_party/blink/renderer/core/animation/view_timeline.h"
|
||||
#include "third_party/blink/renderer/core/animation/timeline_range.h"
|
||||
#include "third_party/blink/renderer/core/css/cssom/css_unit_value.h"
|
||||
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
|
||||
|
||||
@ -57,7 +57,7 @@ void Keyframe::AddKeyframePropertiesToV8Object(V8ObjectBuilder& object_builder,
|
||||
EffectModel::CompositeOperationToString(composite_));
|
||||
}
|
||||
|
||||
bool Keyframe::ResolveTimelineOffset(const ViewTimeline* view_timeline,
|
||||
bool Keyframe::ResolveTimelineOffset(const TimelineRange& timeline_range,
|
||||
double range_start,
|
||||
double range_end) {
|
||||
if (!timeline_offset_) {
|
||||
@ -65,7 +65,7 @@ bool Keyframe::ResolveTimelineOffset(const ViewTimeline* view_timeline,
|
||||
}
|
||||
|
||||
double relative_offset =
|
||||
view_timeline->ToFractionalOffset(timeline_offset_.value());
|
||||
timeline_range.ToFractionalOffset(timeline_offset_.value());
|
||||
double range = range_end - range_start;
|
||||
if (!range) {
|
||||
if (offset_) {
|
||||
@ -107,14 +107,4 @@ bool Keyframe::LessThan(const Member<Keyframe>& a, const Member<Keyframe>& b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Keyframe::ResetOffsetResolvedFromTimeline() {
|
||||
if (!timeline_offset_.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
offset_.reset();
|
||||
computed_offset_ = kNullComputedOffset;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace blink
|
||||
|
@ -25,8 +25,8 @@ using PropertyHandleSet = HashSet<PropertyHandle>;
|
||||
class Element;
|
||||
class ComputedStyle;
|
||||
class CompositorKeyframeValue;
|
||||
class TimelineRange;
|
||||
class V8ObjectBuilder;
|
||||
class ViewTimeline;
|
||||
|
||||
// A base class representing an animation keyframe.
|
||||
//
|
||||
@ -144,16 +144,12 @@ class CORE_EXPORT Keyframe : public GarbageCollected<Keyframe> {
|
||||
// we sort by original index of the keyframe if specified.
|
||||
static bool LessThan(const Member<Keyframe>& a, const Member<Keyframe>& b);
|
||||
|
||||
// Compute the offset if dependent on a view timeline. Returns true if the
|
||||
// Compute the offset if dependent on a timeline range. Returns true if the
|
||||
// offset changed.
|
||||
bool ResolveTimelineOffset(const ViewTimeline* view_timeline,
|
||||
bool ResolveTimelineOffset(const TimelineRange&,
|
||||
double range_start,
|
||||
double range_end);
|
||||
|
||||
// Resets the offset if it depends on a view timeline. Returns true if the
|
||||
// offset was reset.
|
||||
bool ResetOffsetResolvedFromTimeline();
|
||||
|
||||
// Add the properties represented by this keyframe to the given V8 object.
|
||||
//
|
||||
// Subclasses should override this to add the (property, value) pairs they
|
||||
@ -261,9 +257,9 @@ class CORE_EXPORT Keyframe : public GarbageCollected<Keyframe> {
|
||||
absl::optional<double> computed_offset_;
|
||||
// Offsets of the form <name> <percent>. These offsets are layout depending
|
||||
// and need to be re-resolved on a style change affecting the corresponding
|
||||
// view timeline. If the effect is not associated with an animation that is
|
||||
// attached to a view-timeline, then the offset and computed offset will be
|
||||
// null.
|
||||
// timeline range. If the effect is not associated with an animation that is
|
||||
// attached to a timeline with a non-empty timeline range,
|
||||
// then the offset and computed offset will be null.
|
||||
absl::optional<TimelineOffset> timeline_offset_;
|
||||
|
||||
// The original index in the keyframe list is used to resolve ties in the
|
||||
|
@ -378,54 +378,10 @@ bool KeyframeEffectModelBase::SetLogicalPropertyResolutionContext(
|
||||
return changed;
|
||||
}
|
||||
|
||||
void KeyframeEffectModelBase::SetViewTimelineIfRequired(
|
||||
const ViewTimeline* timeline) {
|
||||
if (view_timeline_ == timeline) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_timeline_offset_in_keyframe = false;
|
||||
for (const auto& keyframe : keyframes_) {
|
||||
if (keyframe->GetTimelineOffset()) {
|
||||
has_timeline_offset_in_keyframe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_timeline_offset_in_keyframe) {
|
||||
// Keyframes are essentially immutable once the keyframe model is
|
||||
// constructed. Thus, we should never be in a position where
|
||||
// has_timeline_offset_in_keyframe changes from true to false between
|
||||
// checks, and we should never have a set view timeline that needs to be
|
||||
// cleared.
|
||||
DCHECK(!view_timeline_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (view_timeline_ && !timeline) {
|
||||
// Clear offsets that are resolved from timeline offsets.
|
||||
bool needs_update = false;
|
||||
for (const auto& keyframe : keyframes_) {
|
||||
needs_update |= keyframe->ResetOffsetResolvedFromTimeline();
|
||||
}
|
||||
|
||||
if (needs_update) {
|
||||
std::stable_sort(keyframes_.begin(), keyframes_.end(),
|
||||
&Keyframe::LessThan);
|
||||
ClearCachedData();
|
||||
}
|
||||
}
|
||||
view_timeline_ = timeline;
|
||||
if (timeline) {
|
||||
timeline->ResolveTimelineOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeEffectModelBase::Trace(Visitor* visitor) const {
|
||||
visitor->Trace(keyframes_);
|
||||
visitor->Trace(keyframe_groups_);
|
||||
visitor->Trace(interpolation_effect_);
|
||||
visitor->Trace(view_timeline_);
|
||||
EffectModel::Trace(visitor);
|
||||
}
|
||||
|
||||
@ -537,21 +493,29 @@ void KeyframeEffectModelBase::IndexKeyframesAndResolveComputedOffsets() {
|
||||
}
|
||||
}
|
||||
|
||||
bool KeyframeEffectModelBase::ResolveTimelineOffsets(double range_start,
|
||||
double range_end) {
|
||||
if (!view_timeline_) {
|
||||
bool KeyframeEffectModelBase::ResolveTimelineOffsets(
|
||||
const TimelineRange& timeline_range,
|
||||
double range_start,
|
||||
double range_end) {
|
||||
if (timeline_range == last_timeline_range_ &&
|
||||
last_range_start_ == range_start && last_range_end_ == range_end) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool needs_update = false;
|
||||
for (const auto& keyframe : keyframes_) {
|
||||
needs_update |=
|
||||
keyframe->ResolveTimelineOffset(view_timeline_, range_start, range_end);
|
||||
keyframe->ResolveTimelineOffset(timeline_range, range_start, range_end);
|
||||
}
|
||||
if (needs_update) {
|
||||
std::stable_sort(keyframes_.begin(), keyframes_.end(), &Keyframe::LessThan);
|
||||
ClearCachedData();
|
||||
}
|
||||
|
||||
last_timeline_range_ = timeline_range;
|
||||
last_range_start_ = range_start;
|
||||
last_range_end_ = range_end;
|
||||
|
||||
return needs_update;
|
||||
}
|
||||
|
||||
@ -560,6 +524,10 @@ void KeyframeEffectModelBase::ClearCachedData() {
|
||||
interpolation_effect_->Clear();
|
||||
last_fraction_ = std::numeric_limits<double>::quiet_NaN();
|
||||
needs_compositor_keyframes_snapshot_ = true;
|
||||
|
||||
last_timeline_range_ = absl::nullopt;
|
||||
last_range_start_ = absl::nullopt;
|
||||
last_range_end_ = absl::nullopt;
|
||||
}
|
||||
|
||||
bool KeyframeEffectModelBase::IsReplaceOnly() const {
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "third_party/blink/renderer/core/animation/interpolation_effect.h"
|
||||
#include "third_party/blink/renderer/core/animation/property_handle.h"
|
||||
#include "third_party/blink/renderer/core/animation/string_keyframe.h"
|
||||
#include "third_party/blink/renderer/core/animation/timeline_range.h"
|
||||
#include "third_party/blink/renderer/core/animation/transition_keyframe.h"
|
||||
#include "third_party/blink/renderer/core/core_export.h"
|
||||
#include "third_party/blink/renderer/platform/animation/timing_function.h"
|
||||
@ -171,12 +172,12 @@ class CORE_EXPORT KeyframeEffectModelBase : public EffectModel {
|
||||
|
||||
virtual KeyframeEffectModelBase* Clone() = 0;
|
||||
|
||||
void SetViewTimelineIfRequired(const ViewTimeline* timeline);
|
||||
|
||||
// Ensure timeline offsets are properly resolved. If any of the offsets
|
||||
// changed, the keyframes are resorted and cached data is cleared. Returns
|
||||
// true if one or more offsets were affected.
|
||||
bool ResolveTimelineOffsets(double range_start, double range_end);
|
||||
bool ResolveTimelineOffsets(const TimelineRange&,
|
||||
double range_start,
|
||||
double range_end);
|
||||
|
||||
void Trace(Visitor*) const override;
|
||||
|
||||
@ -243,7 +244,11 @@ class CORE_EXPORT KeyframeEffectModelBase : public EffectModel {
|
||||
mutable bool has_revert_ = false;
|
||||
mutable bool has_named_range_keyframes_ = false;
|
||||
|
||||
Member<const ViewTimeline> view_timeline_;
|
||||
// The timeline and animation ranges last used to resolve
|
||||
// named range offsets. (See ResolveTimelineOffsets).
|
||||
absl::optional<TimelineRange> last_timeline_range_;
|
||||
absl::optional<double> last_range_start_;
|
||||
absl::optional<double> last_range_end_;
|
||||
|
||||
friend class KeyframeEffectModelTest;
|
||||
};
|
||||
|
@ -131,6 +131,10 @@ absl::optional<ScrollOffsets> ScrollTimeline::GetResolvedScrollOffsets() const {
|
||||
return timeline_state_snapshotted_.scroll_offsets;
|
||||
}
|
||||
|
||||
absl::optional<ScrollOffsets> ScrollTimeline::GetResolvedViewOffsets() const {
|
||||
return timeline_state_snapshotted_.view_offsets;
|
||||
}
|
||||
|
||||
// TODO(crbug.com/1336260): Since phase can only be kActive or kInactive and
|
||||
// currentTime can only be null if phase is inactive or before the first
|
||||
// snapshot we can probably drop phase.
|
||||
@ -272,6 +276,12 @@ AnimationTimeDelta ScrollTimeline::CalculateIntrinsicIterationDuration(
|
||||
return AnimationTimeDelta();
|
||||
}
|
||||
|
||||
TimelineRange ScrollTimeline::GetTimelineRange() const {
|
||||
absl::optional<ScrollOffsets> scroll_offsets = GetResolvedScrollOffsets();
|
||||
return scroll_offsets.has_value() ? TimelineRange(scroll_offsets.value())
|
||||
: TimelineRange();
|
||||
}
|
||||
|
||||
void ScrollTimeline::ServiceAnimations(TimingUpdateReason reason) {
|
||||
// When scroll timeline goes from inactive to active the animations may need
|
||||
// to be started and possibly composited.
|
||||
@ -422,13 +432,15 @@ void ScrollTimeline::InvalidateEffectTargetStyle() const {
|
||||
}
|
||||
|
||||
bool ScrollTimeline::ValidateSnapshot() {
|
||||
auto state = ComputeTimelineState();
|
||||
if (ValidateTimelineOffsets() && timeline_state_snapshotted_ == state) {
|
||||
// ValidateTimelineOffsets will resolve timeline offsets according to
|
||||
// data in `timeline_state_snapshotted_`, so that needs to be updated
|
||||
// before the call.
|
||||
TimelineState old_state = timeline_state_snapshotted_;
|
||||
timeline_state_snapshotted_ = ComputeTimelineState();
|
||||
if (ValidateTimelineOffsets() && old_state == timeline_state_snapshotted_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
timeline_state_snapshotted_ = state;
|
||||
|
||||
// Mark an attached animation's target as dirty if the play state is running
|
||||
// or finished. Idle animations are not in effect and the effect of a paused
|
||||
// animation is not impacted by timeline staleness.
|
||||
|
@ -89,6 +89,8 @@ class CORE_EXPORT ScrollTimeline : public AnimationTimeline,
|
||||
return CalculateIntrinsicIterationDuration(nullptr, timing);
|
||||
}
|
||||
|
||||
TimelineRange GetTimelineRange() const override;
|
||||
|
||||
AnimationTimeDelta ZeroTime() override { return AnimationTimeDelta(); }
|
||||
|
||||
void ServiceAnimations(TimingUpdateReason) override;
|
||||
@ -108,9 +110,10 @@ class CORE_EXPORT ScrollTimeline : public AnimationTimeline,
|
||||
// removed before the ScrollTimeline was created.
|
||||
Node* ResolvedSource() const { return resolved_source_; }
|
||||
|
||||
// Return the latest resolved scroll offsets. This will be empty when
|
||||
// Return the latest resolved scroll/view offsets. This will be empty when
|
||||
// timeline is inactive.
|
||||
absl::optional<ScrollOffsets> GetResolvedScrollOffsets() const;
|
||||
absl::optional<ScrollOffsets> GetResolvedViewOffsets() const;
|
||||
|
||||
float GetResolvedZoom() const { return timeline_state_snapshotted_.zoom; }
|
||||
|
||||
|
@ -73,30 +73,39 @@ class ScrollTimelineTest : public RenderingTest {
|
||||
|
||||
class TestScrollTimeline : public ScrollTimeline {
|
||||
public:
|
||||
TestScrollTimeline(Document* document, Element* source)
|
||||
TestScrollTimeline(Document* document, Element* source, bool snapshot = true)
|
||||
: ScrollTimeline(document,
|
||||
TimelineAttachment::kLocal,
|
||||
ScrollTimeline::ReferenceType::kSource,
|
||||
source,
|
||||
ScrollAxis::kVertical) {
|
||||
UpdateSnapshot();
|
||||
if (snapshot) {
|
||||
UpdateSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
void Trace(Visitor* visitor) const override {
|
||||
ScrollTimeline::Trace(visitor);
|
||||
}
|
||||
|
||||
// UpdateSnapshot has 'protected' visibility.
|
||||
void UpdateSnapshotForTesting() { UpdateSnapshot(); }
|
||||
};
|
||||
|
||||
class TestViewTimeline : public ViewTimeline {
|
||||
public:
|
||||
TestViewTimeline(Document* document, Element* subject)
|
||||
TestViewTimeline(Document* document, Element* subject, bool snapshot = true)
|
||||
: ViewTimeline(document,
|
||||
TimelineAttachment::kLocal,
|
||||
subject,
|
||||
ScrollAxis::kVertical,
|
||||
TimelineInset()) {
|
||||
UpdateSnapshot();
|
||||
if (snapshot) {
|
||||
UpdateSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSnapshotForTesting() { UpdateSnapshot(); }
|
||||
};
|
||||
|
||||
TEST_F(ScrollTimelineTest, CurrentTimeIsNullIfSourceIsNotScrollable) {
|
||||
@ -864,4 +873,69 @@ TEST_F(ScrollTimelineTest, ViewTimelineOffsetZoom) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ScrollTimelineTest, ScrollTimelineGetTimelineRange) {
|
||||
SetBodyInnerHTML(R"HTML(
|
||||
<style>
|
||||
#scroller {
|
||||
overflow-y: auto;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.spacer {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id='scroller'>
|
||||
<div class='spacer'></div>
|
||||
</div>
|
||||
)HTML");
|
||||
|
||||
auto* timeline = MakeGarbageCollected<TestScrollTimeline>(
|
||||
&GetDocument(), GetElementById("scroller"), /* snapshot */ false);
|
||||
|
||||
// GetTimelineRange before taking a snapshot.
|
||||
EXPECT_TRUE(timeline->GetTimelineRange().IsEmpty());
|
||||
|
||||
timeline->UpdateSnapshotForTesting();
|
||||
EXPECT_EQ(TimelineRange(TimelineRange::ScrollOffsets(0, 300)),
|
||||
timeline->GetTimelineRange());
|
||||
}
|
||||
|
||||
TEST_F(ScrollTimelineTest, ViewTimelineGetTimelineRange) {
|
||||
SetBodyInnerHTML(R"HTML(
|
||||
<style>
|
||||
#scroller {
|
||||
overflow-y: auto;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 20px solid black;
|
||||
}
|
||||
.spacer {
|
||||
height: 200px;
|
||||
}
|
||||
#subject {
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id='scroller'>
|
||||
<div class='spacer'></div>
|
||||
<div id='subject'></div>
|
||||
<div class='spacer'></div>
|
||||
</div>
|
||||
)HTML");
|
||||
|
||||
auto* timeline = MakeGarbageCollected<TestViewTimeline>(
|
||||
&GetDocument(), GetElementById("subject"), /* snapshot */ false);
|
||||
|
||||
// GetTimelineRange before taking a snapshot.
|
||||
EXPECT_TRUE(timeline->GetTimelineRange().IsEmpty());
|
||||
|
||||
timeline->UpdateSnapshotForTesting();
|
||||
EXPECT_EQ(TimelineRange(TimelineRange::ScrollOffsets(100, 300),
|
||||
/* subject_size */ 100),
|
||||
timeline->GetTimelineRange());
|
||||
}
|
||||
|
||||
} // namespace blink
|
||||
|
116
third_party/blink/renderer/core/animation/timeline_range.cc
vendored
Normal file
116
third_party/blink/renderer/core/animation/timeline_range.cc
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "third_party/blink/renderer/core/animation/timeline_range.h"
|
||||
|
||||
#include "third_party/blink/renderer/core/animation/timeline_offset.h"
|
||||
#include "third_party/blink/renderer/core/animation/timing_calculations.h"
|
||||
#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
|
||||
#include "third_party/blink/renderer/platform/geometry/length_functions.h"
|
||||
|
||||
namespace blink {
|
||||
|
||||
bool TimelineRange::IsEmpty() const {
|
||||
return LessThanOrEqualToWithinEpsilon(offsets_.end - offsets_.start, 0.0);
|
||||
}
|
||||
|
||||
double TimelineRange::ToFractionalOffset(
|
||||
const TimelineOffset& timeline_offset) const {
|
||||
if (IsEmpty()) {
|
||||
// This is either a monotonic timeline or an inactive ScrollTimeline.
|
||||
return 0.0;
|
||||
}
|
||||
double full_range_size = offsets_.end - offsets_.start;
|
||||
|
||||
ScrollOffsets range(0, 0);
|
||||
|
||||
if (subject_size_ == 0) {
|
||||
// This is a non-view ScrollTimeline, or it can also be a ViewTimeline
|
||||
// that happens have subject with size=0.
|
||||
range = {offsets_.start, offsets_.end};
|
||||
} else {
|
||||
range = ConvertNamedRange(timeline_offset.name);
|
||||
}
|
||||
|
||||
DCHECK_GT(full_range_size, 0);
|
||||
|
||||
double offset =
|
||||
range.start + MinimumValueForLength(timeline_offset.offset,
|
||||
LayoutUnit(range.end - range.start));
|
||||
return (offset - offsets_.start) / full_range_size;
|
||||
}
|
||||
|
||||
TimelineRange::ScrollOffsets TimelineRange::ConvertNamedRange(
|
||||
NamedRange named_range) const {
|
||||
// https://drafts.csswg.org/scroll-animations-1/#view-timelines-ranges
|
||||
double align_subject_start_view_end = offsets_.start;
|
||||
double align_subject_end_view_start = offsets_.end;
|
||||
double align_subject_start_view_start =
|
||||
align_subject_end_view_start - subject_size_;
|
||||
double align_subject_end_view_end =
|
||||
align_subject_start_view_end + subject_size_;
|
||||
|
||||
switch (named_range) {
|
||||
case TimelineOffset::NamedRange::kNone:
|
||||
case TimelineOffset::NamedRange::kCover:
|
||||
// Represents the full range of the view progress timeline:
|
||||
// 0% progress represents the position at which the start border edge of
|
||||
// the element’s principal box coincides with the end edge of its view
|
||||
// progress visibility range.
|
||||
// 100% progress represents the position at which the end border edge of
|
||||
// the element’s principal box coincides with the start edge of its view
|
||||
// progress visibility range.
|
||||
return {align_subject_start_view_end, align_subject_end_view_start};
|
||||
|
||||
case TimelineOffset::NamedRange::kContain:
|
||||
// Represents the range during which the principal box is either fully
|
||||
// contained by, or fully covers, its view progress visibility range
|
||||
// within the scrollport.
|
||||
// 0% progress represents the earlier position at which:
|
||||
// 1. the start border edge of the element’s principal box coincides
|
||||
// with the start edge of its view progress visibility range.
|
||||
// 2. the end border edge of the element’s principal box coincides with
|
||||
// the end edge of its view progress visibility range.
|
||||
// 100% progress represents the later position at which:
|
||||
// 1. the start border edge of the element’s principal box coincides
|
||||
// with the start edge of its view progress visibility range.
|
||||
// 2. the end border edge of the element’s principal box coincides with
|
||||
// the end edge of its view progress visibility range.
|
||||
return {
|
||||
std::min(align_subject_start_view_start, align_subject_end_view_end),
|
||||
std::max(align_subject_start_view_start, align_subject_end_view_end)};
|
||||
|
||||
case TimelineOffset::NamedRange::kEntry:
|
||||
// Represents the range during which the principal box is entering the
|
||||
// view progress visibility range.
|
||||
// 0% is equivalent to 0% of the cover range.
|
||||
// 100% is equivalent to 0% of the contain range.
|
||||
return {
|
||||
align_subject_start_view_end,
|
||||
std::min(align_subject_start_view_start, align_subject_end_view_end)};
|
||||
|
||||
case TimelineOffset::NamedRange::kEntryCrossing:
|
||||
// Represents the range during which the principal box is crossing the
|
||||
// entry edge of the viewport.
|
||||
// 0% is equivalent to 0% of the cover range.
|
||||
return {align_subject_start_view_end, align_subject_end_view_end};
|
||||
|
||||
case TimelineOffset::NamedRange::kExit:
|
||||
// Represents the range during which the principal box is exiting the view
|
||||
// progress visibility range.
|
||||
// 0% is equivalent to 100% of the contain range.
|
||||
// 100% is equivalent to 100% of the cover range.
|
||||
return {
|
||||
std::max(align_subject_start_view_start, align_subject_end_view_end),
|
||||
align_subject_end_view_start};
|
||||
|
||||
case TimelineOffset::NamedRange::kExitCrossing:
|
||||
// Represents the range during which the principal box is exiting the view
|
||||
// progress visibility range.
|
||||
// 100% is equivalent to 100% of the cover range.
|
||||
return {align_subject_start_view_start, align_subject_end_view_start};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blink
|
73
third_party/blink/renderer/core/animation/timeline_range.h
vendored
Normal file
73
third_party/blink/renderer/core/animation/timeline_range.h
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_TIMELINE_RANGE_H_
|
||||
#define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_TIMELINE_RANGE_H_
|
||||
|
||||
#include "cc/animation/scroll_timeline.h"
|
||||
#include "third_party/blink/renderer/bindings/core/v8/v8_timeline_range.h"
|
||||
#include "third_party/blink/renderer/core/core_export.h"
|
||||
|
||||
namespace blink {
|
||||
|
||||
struct TimelineOffset;
|
||||
|
||||
// A TimelineRange represents a given scroll range within an associated
|
||||
// scroller's minimum/maximum scroll. This is useful for ViewTimelines
|
||||
// in particular, because they represent exactly that: a segment of a (non-view)
|
||||
// ScrollTimeline.
|
||||
//
|
||||
// The primary job of TimelineRange is to convert offsets within an animation
|
||||
// attachment range [1] (represented by TimelineOffset values) to fractional
|
||||
// offsets within the TimelineRange. See TimelineRange::ToFractionalOffset.
|
||||
//
|
||||
// It may be helpful to think about a TimelineRange (which is timeline-specific)
|
||||
// as a sub-range of the scroller's full range, and an animation attachment
|
||||
// range (which is animation specific) as a sub-range of that TimelineRange.
|
||||
//
|
||||
// - For ViewTimelines, the start/end offsets will correspond to the scroll
|
||||
// range that would cause the scrollport to intersect with the subject
|
||||
// element's box.
|
||||
// - For (non-view) ScrollTimelines, the start/end offset is always the
|
||||
// same as the minimum/maximum scroll.
|
||||
// - For monotonic timelines, the TimelineRange is always empty.
|
||||
//
|
||||
// [1]
|
||||
// https://drafts.csswg.org/scroll-animations-1/#named-range-animation-declaration
|
||||
class CORE_EXPORT TimelineRange {
|
||||
public:
|
||||
using ScrollOffsets = cc::ScrollTimeline::ScrollOffsets;
|
||||
using NamedRange = V8TimelineRange::Enum;
|
||||
|
||||
TimelineRange() = default;
|
||||
// The subject_size is the size of the subject element for ViewTimelines.
|
||||
// It should be zero for other timelines.
|
||||
explicit TimelineRange(ScrollOffsets offsets, double subject_size = 0)
|
||||
: offsets_(offsets), subject_size_(subject_size) {}
|
||||
|
||||
bool operator==(const TimelineRange& other) const {
|
||||
return offsets_ == other.offsets_ && subject_size_ == other.subject_size_;
|
||||
}
|
||||
|
||||
bool operator!=(const TimelineRange& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool IsEmpty() const;
|
||||
|
||||
// Converts an offset within some animation attachment range to a fractional
|
||||
// offset within this TimelineRange.
|
||||
double ToFractionalOffset(const TimelineOffset&) const;
|
||||
|
||||
private:
|
||||
// https://drafts.csswg.org/scroll-animations-1/#view-timelines-ranges
|
||||
ScrollOffsets ConvertNamedRange(NamedRange) const;
|
||||
|
||||
ScrollOffsets offsets_;
|
||||
double subject_size_ = 0;
|
||||
};
|
||||
|
||||
} // namespace blink
|
||||
|
||||
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_TIMELINE_RANGE_H_
|
@ -281,6 +281,18 @@ AnimationTimeDelta ViewTimeline::CalculateIntrinsicIterationDuration(
|
||||
return AnimationTimeDelta();
|
||||
}
|
||||
|
||||
TimelineRange ViewTimeline::GetTimelineRange() const {
|
||||
absl::optional<ScrollOffsets> scroll_offsets = GetResolvedScrollOffsets();
|
||||
absl::optional<ScrollOffsets> view_offsets = GetResolvedViewOffsets();
|
||||
|
||||
if (!scroll_offsets.has_value() || !view_offsets.has_value()) {
|
||||
return TimelineRange();
|
||||
}
|
||||
|
||||
double subject_size = view_offsets->end - view_offsets->start;
|
||||
return TimelineRange(scroll_offsets.value(), subject_size);
|
||||
}
|
||||
|
||||
void ViewTimeline::CalculateOffsets(PaintLayerScrollableArea* scrollable_area,
|
||||
ScrollOrientation physical_orientation,
|
||||
TimelineState* state) const {
|
||||
@ -294,20 +306,20 @@ void ViewTimeline::CalculateOffsets(PaintLayerScrollableArea* scrollable_area,
|
||||
subject_size_ = SubjectSize();
|
||||
|
||||
DCHECK(subject_position_);
|
||||
target_offset_ = physical_orientation == kHorizontalScroll
|
||||
? subject_position_->x()
|
||||
: subject_position_->y();
|
||||
double target_offset = physical_orientation == kHorizontalScroll
|
||||
? subject_position_->x()
|
||||
: subject_position_->y();
|
||||
|
||||
DCHECK(subject_size_);
|
||||
double target_size;
|
||||
LayoutUnit viewport_size;
|
||||
if (physical_orientation == kHorizontalScroll) {
|
||||
target_size_ = subject_size_->Width().ToDouble();
|
||||
target_size = subject_size_->Width().ToDouble();
|
||||
viewport_size = scrollable_area->LayoutContentRect().Width();
|
||||
} else {
|
||||
target_size_ = subject_size_->Height().ToDouble();
|
||||
target_size = subject_size_->Height().ToDouble();
|
||||
viewport_size = scrollable_area->LayoutContentRect().Height();
|
||||
}
|
||||
viewport_size_ = viewport_size.ToDouble();
|
||||
|
||||
Element* source = CurrentAttachment()->ComputeSourceNoLayout();
|
||||
DCHECK(source);
|
||||
@ -334,16 +346,18 @@ void ViewTimeline::CalculateOffsets(PaintLayerScrollableArea* scrollable_area,
|
||||
// source box, whereas "start offset" refers to the start of the timeline,
|
||||
// and similarly for end side/offset.
|
||||
// [1] https://drafts.csswg.org/css-writing-modes-4/#css-start
|
||||
end_side_inset_ = ComputeInset(inset.GetEnd(), viewport_size);
|
||||
start_side_inset_ = ComputeInset(inset.GetStart(), viewport_size);
|
||||
double end_side_inset = ComputeInset(inset.GetEnd(), viewport_size);
|
||||
double start_side_inset = ComputeInset(inset.GetStart(), viewport_size);
|
||||
|
||||
double start_offset = target_offset_ - viewport_size_ + end_side_inset_;
|
||||
double end_offset = target_offset_ + target_size_ - start_side_inset_;
|
||||
double viewport_size_double = viewport_size.ToDouble();
|
||||
|
||||
double start_offset = target_offset - viewport_size_double + end_side_inset;
|
||||
double end_offset = target_offset + target_size - start_side_inset;
|
||||
|
||||
state->scroll_offsets =
|
||||
absl::make_optional<ScrollOffsets>(start_offset, end_offset);
|
||||
state->view_offsets = absl::make_optional<ScrollOffsets>(
|
||||
target_offset_, target_offset_ + target_size_);
|
||||
target_offset, target_offset + target_size);
|
||||
}
|
||||
|
||||
absl::optional<LayoutSize> ViewTimeline::SubjectSize() const {
|
||||
@ -469,101 +483,7 @@ const TimelineInset& ViewTimeline::GetInset() const {
|
||||
|
||||
double ViewTimeline::ToFractionalOffset(
|
||||
const TimelineOffset& timeline_offset) const {
|
||||
// https://drafts.csswg.org/scroll-animations-1/#view-timelines-ranges
|
||||
double align_subject_start_view_end =
|
||||
target_offset_ - viewport_size_ + end_side_inset_;
|
||||
double align_subject_end_view_start =
|
||||
target_offset_ + target_size_ - start_side_inset_;
|
||||
double align_subject_start_view_start =
|
||||
align_subject_end_view_start - target_size_;
|
||||
double align_subject_end_view_end =
|
||||
align_subject_start_view_end + target_size_;
|
||||
// Timeline is inactive if scroll range is zero.
|
||||
double range = align_subject_end_view_start - align_subject_start_view_end;
|
||||
if (!range) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double range_start = 0;
|
||||
double range_end = 0;
|
||||
switch (timeline_offset.name) {
|
||||
case TimelineOffset::NamedRange::kNone:
|
||||
case TimelineOffset::NamedRange::kCover:
|
||||
// Represents the full range of the view progress timeline:
|
||||
// 0% progress represents the position at which the start border edge of
|
||||
// the element’s principal box coincides with the end edge of its view
|
||||
// progress visibility range.
|
||||
// 100% progress represents the position at which the end border edge of
|
||||
// the element’s principal box coincides with the start edge of its view
|
||||
// progress visibility range.
|
||||
range_start = align_subject_start_view_end;
|
||||
range_end = align_subject_end_view_start;
|
||||
break;
|
||||
|
||||
case TimelineOffset::NamedRange::kContain:
|
||||
// Represents the range during which the principal box is either fully
|
||||
// contained by, or fully covers, its view progress visibility range
|
||||
// within the scrollport.
|
||||
// 0% progress represents the earlier position at which:
|
||||
// 1. the start border edge of the element’s principal box coincides
|
||||
// with the start edge of its view progress visibility range.
|
||||
// 2. the end border edge of the element’s principal box coincides with
|
||||
// the end edge of its view progress visibility range.
|
||||
// 100% progress represents the later position at which:
|
||||
// 1. the start border edge of the element’s principal box coincides
|
||||
// with the start edge of its view progress visibility range.
|
||||
// 2. the end border edge of the element’s principal box coincides with
|
||||
// the end edge of its view progress visibility range.
|
||||
range_start =
|
||||
std::min(align_subject_start_view_start, align_subject_end_view_end);
|
||||
range_end =
|
||||
std::max(align_subject_start_view_start, align_subject_end_view_end);
|
||||
break;
|
||||
|
||||
case TimelineOffset::NamedRange::kEntry:
|
||||
// Represents the range during which the principal box is entering the
|
||||
// view progress visibility range.
|
||||
// 0% is equivalent to 0% of the cover range.
|
||||
// 100% is equivalent to 0% of the contain range.
|
||||
range_start = align_subject_start_view_end;
|
||||
range_end =
|
||||
std::min(align_subject_start_view_start, align_subject_end_view_end);
|
||||
break;
|
||||
|
||||
case TimelineOffset::NamedRange::kEntryCrossing:
|
||||
// Represents the range during which the principal box is crossing the
|
||||
// entry edge of the viewport.
|
||||
// 0% is equivalent to 0% of the cover range.
|
||||
range_start = align_subject_start_view_end;
|
||||
range_end = align_subject_end_view_end;
|
||||
break;
|
||||
|
||||
case TimelineOffset::NamedRange::kExit:
|
||||
// Represents the range during which the principal box is exiting the view
|
||||
// progress visibility range.
|
||||
// 0% is equivalent to 100% of the contain range.
|
||||
// 100% is equivalent to 100% of the cover range.
|
||||
range_start =
|
||||
std::max(align_subject_start_view_start, align_subject_end_view_end);
|
||||
range_end = align_subject_end_view_start;
|
||||
break;
|
||||
|
||||
case TimelineOffset::NamedRange::kExitCrossing:
|
||||
// Represents the range during which the principal box is exiting the view
|
||||
// progress visibility range.
|
||||
// 100% is equivalent to 100% of the cover range.
|
||||
range_start = align_subject_start_view_start;
|
||||
range_end = align_subject_end_view_start;
|
||||
break;
|
||||
}
|
||||
|
||||
DCHECK(range_end >= range_start);
|
||||
DCHECK_GT(range, 0);
|
||||
|
||||
double offset =
|
||||
range_start + MinimumValueForLength(timeline_offset.offset,
|
||||
LayoutUnit(range_end - range_start));
|
||||
return (offset - align_subject_start_view_end) / range;
|
||||
return GetTimelineRange().ToFractionalOffset(timeline_offset);
|
||||
}
|
||||
|
||||
CSSNumericValue* ViewTimeline::startOffset() const {
|
||||
@ -611,33 +531,14 @@ bool ViewTimeline::CheckIfNeedsValidation() {
|
||||
}
|
||||
|
||||
bool ViewTimeline::ResolveTimelineOffsets() const {
|
||||
TimelineRange timeline_range = GetTimelineRange();
|
||||
bool has_keyframe_update = false;
|
||||
for (Animation* animation : GetAnimations()) {
|
||||
if (auto* effect = DynamicTo<KeyframeEffect>(animation->effect())) {
|
||||
double range_start =
|
||||
animation->GetRangeStartInternal()
|
||||
? ToFractionalOffset(animation->GetRangeStartInternal().value())
|
||||
: 0;
|
||||
double range_end =
|
||||
animation->GetRangeEndInternal()
|
||||
? ToFractionalOffset(animation->GetRangeEndInternal().value())
|
||||
: 1;
|
||||
if (effect->Model()->ResolveTimelineOffsets(range_start, range_end)) {
|
||||
has_keyframe_update = true;
|
||||
}
|
||||
}
|
||||
has_keyframe_update |= animation->ResolveTimelineOffsets(timeline_range);
|
||||
}
|
||||
return has_keyframe_update;
|
||||
}
|
||||
|
||||
Animation* ViewTimeline::Play(AnimationEffect* effect,
|
||||
ExceptionState& exception_state) {
|
||||
if (auto* keyframe_effect = DynamicTo<KeyframeEffect>(effect)) {
|
||||
keyframe_effect->Model()->SetViewTimelineIfRequired(this);
|
||||
}
|
||||
return AnimationTimeline::Play(effect, exception_state);
|
||||
}
|
||||
|
||||
void ViewTimeline::Trace(Visitor* visitor) const {
|
||||
visitor->Trace(style_dependant_start_inset_);
|
||||
visitor->Trace(style_dependant_end_inset_);
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h"
|
||||
#include "third_party/blink/renderer/core/animation/scroll_timeline.h"
|
||||
#include "third_party/blink/renderer/core/animation/timeline_inset.h"
|
||||
#include "third_party/blink/renderer/core/animation/timeline_range.h"
|
||||
#include "third_party/blink/renderer/core/core_export.h"
|
||||
#include "third_party/blink/renderer/core/layout/layout_box.h"
|
||||
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
|
||||
@ -50,6 +51,8 @@ class CORE_EXPORT ViewTimeline : public ScrollTimeline {
|
||||
const absl::optional<TimelineOffset>& rangeEnd,
|
||||
const Timing&) override;
|
||||
|
||||
TimelineRange GetTimelineRange() const override;
|
||||
|
||||
// IDL API implementation.
|
||||
Element* subject() const;
|
||||
|
||||
@ -60,18 +63,11 @@ class CORE_EXPORT ViewTimeline : public ScrollTimeline {
|
||||
|
||||
const TimelineInset& GetInset() const;
|
||||
|
||||
// Converts a delay that is expressed as a (phase,percentage) pair to
|
||||
// a fractional offset.
|
||||
double ToFractionalOffset(const TimelineOffset& timeline_offset) const;
|
||||
|
||||
CSSNumericValue* startOffset() const;
|
||||
CSSNumericValue* endOffset() const;
|
||||
|
||||
bool ResolveTimelineOffsets() const;
|
||||
|
||||
Animation* Play(AnimationEffect*,
|
||||
ExceptionState& = ASSERT_NO_EXCEPTION) override;
|
||||
|
||||
void Trace(Visitor*) const override;
|
||||
|
||||
protected:
|
||||
@ -89,12 +85,8 @@ class CORE_EXPORT ViewTimeline : public ScrollTimeline {
|
||||
absl::optional<gfx::PointF> SubjectPosition() const;
|
||||
|
||||
private:
|
||||
// Cache values to make timeline phase conversions more efficient.
|
||||
mutable double target_offset_;
|
||||
mutable double target_size_;
|
||||
mutable double viewport_size_;
|
||||
mutable double start_side_inset_;
|
||||
mutable double end_side_inset_;
|
||||
double ToFractionalOffset(const TimelineOffset& timeline_offset) const;
|
||||
|
||||
// Cache values for post-layout validation check. If the subject position or
|
||||
// size changes, then the range boundaries are stale.
|
||||
mutable absl::optional<LayoutSize> subject_size_;
|
||||
|
Reference in New Issue
Block a user