Commit 9ae8c338 authored by Kevin Ellis's avatar Kevin Ellis Committed by Commit Bot

Fix handling of replaceable animations with multiple timelines.

When removing a replaced animation, it is important to consider all
timelines.  Previously, we made the decision on a per timeline basis,
resulting in failing to flag some animations as replaceable.

Bug: 10293484

Change-Id: I8256f2eb69f710ba1ec04b095446bd30c1b64a97
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2401505Reviewed-by: default avatarXida Chen <xidachen@chromium.org>
Commit-Queue: Kevin Ellis <kevers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#805499}
parent 285ea59f
...@@ -117,18 +117,11 @@ void AnimationTimeline::ServiceAnimations(TimingUpdateReason reason) { ...@@ -117,18 +117,11 @@ void AnimationTimeline::ServiceAnimations(TimingUpdateReason reason) {
// Explicitly free the backing store to avoid memory regressions. // Explicitly free the backing store to avoid memory regressions.
// TODO(bikineev): Revisit when young generation is done. // TODO(bikineev): Revisit when young generation is done.
animations.clear(); animations.clear();
if (RuntimeEnabledFeatures::WebAnimationsAPIEnabled() &&
reason == kTimingUpdateForAnimationFrame) {
RemoveReplacedAnimations();
}
} }
// https://drafts.csswg.org/web-animations-1/#removing-replaced-animations // https://drafts.csswg.org/web-animations-1/#removing-replaced-animations
void AnimationTimeline::RemoveReplacedAnimations() { void AnimationTimeline::getReplaceableAnimations(
// Group replaceable animations by target element. AnimationTimeline::ReplaceableAnimationsMap* replaceable_animations_map) {
HeapHashMap<Member<Element>, Member<HeapVector<Member<Animation>>>>
replaceable_animations;
for (Animation* animation : animations_) { for (Animation* animation : animations_) {
// Initial conditions for removal: // Initial conditions for removal:
// * has an associated animation effect whose effect target is a descendant // * has an associated animation effect whose effect target is a descendant
...@@ -142,62 +135,13 @@ void AnimationTimeline::RemoveReplacedAnimations() { ...@@ -142,62 +135,13 @@ void AnimationTimeline::RemoveReplacedAnimations() {
if (target->GetDocument() != animation->GetDocument()) if (target->GetDocument() != animation->GetDocument())
continue; continue;
auto inserted = replaceable_animations.insert(target, nullptr); auto inserted = replaceable_animations_map->insert(target, nullptr);
if (inserted.is_new_entry) { if (inserted.is_new_entry) {
inserted.stored_value->value = inserted.stored_value->value =
MakeGarbageCollected<HeapVector<Member<Animation>>>(); MakeGarbageCollected<HeapVector<Member<Animation>>>();
} }
inserted.stored_value->value->push_back(animation); inserted.stored_value->value->push_back(animation);
} }
HeapVector<Member<Animation>> animations_to_remove;
for (auto& elem_it : replaceable_animations) {
HeapVector<Member<Animation>>* animations = elem_it.value;
// Only elements with multiple animations in the replaceable state need to
// be checked.
if (animations->size() == 1)
continue;
// By processing in decreasing order by priority, we can perform a single
// pass for discovery of replaced properties.
std::sort(animations->begin(), animations->end(), CompareAnimations);
PropertyHandleSet replaced_properties;
for (auto anim_it = animations->rbegin(); anim_it != animations->rend();
anim_it++) {
// Remaining conditions for removal:
// * has a replace state of active, and
// * for which there exists for each target property of every animation
// effect associated with animation, an animation effect associated with
// a replaceable animation with a higher composite order than animation
// that includes the same target property.
// Only active animations can be removed. We still need to go through
// the process of iterating over properties if not removable to update
// the set of properties being replaced.
bool replace = (*anim_it)->ReplaceStateActive();
PropertyHandleSet animation_properties =
To<KeyframeEffect>((*anim_it)->effect())->Model()->Properties();
for (const auto& property : animation_properties) {
auto inserted = replaced_properties.insert(property);
if (inserted.is_new_entry) {
// Top-most compositor order animation affecting this property.
replace = false;
}
}
if (replace)
animations_to_remove.push_back(*anim_it);
}
}
// The list of animations for removal is constructed in reverse composite
// ordering for efficiency. Flip the ordering to ensure that events are
// dispatched in composite order.
// TODO(crbug.com/981905): Add test for ordering once onremove is implemented.
for (auto it = animations_to_remove.rbegin();
it != animations_to_remove.rend(); it++) {
(*it)->RemoveReplacedAnimation();
}
} }
void AnimationTimeline::SetOutdatedAnimation(Animation* animation) { void AnimationTimeline::SetOutdatedAnimation(Animation* animation) {
......
...@@ -94,11 +94,15 @@ class CORE_EXPORT AnimationTimeline : public ScriptWrappable { ...@@ -94,11 +94,15 @@ class CORE_EXPORT AnimationTimeline : public ScriptWrappable {
void MarkAnimationsCompositorPending(bool source_changed = false); void MarkAnimationsCompositorPending(bool source_changed = false);
using ReplaceableAnimationsMap =
HeapHashMap<Member<Element>, Member<HeapVector<Member<Animation>>>>;
void getReplaceableAnimations(
ReplaceableAnimationsMap* replaceable_animation_set);
void Trace(Visitor*) const override; void Trace(Visitor*) const override;
protected: protected:
virtual PhaseAndTime CurrentPhaseAndTime() = 0; virtual PhaseAndTime CurrentPhaseAndTime() = 0;
void RemoveReplacedAnimations();
Member<Document> document_; Member<Document> document_;
unsigned outdated_animation_count_; unsigned outdated_animation_count_;
......
...@@ -73,14 +73,22 @@ void DocumentAnimations::AddTimeline(AnimationTimeline& timeline) { ...@@ -73,14 +73,22 @@ void DocumentAnimations::AddTimeline(AnimationTimeline& timeline) {
} }
void DocumentAnimations::UpdateAnimationTimingForAnimationFrame() { void DocumentAnimations::UpdateAnimationTimingForAnimationFrame() {
// https://drafts.csswg.org/web-animations-1/#timelines.
// 1. Update the current time of all timelines associated with doc passing now
// as the timestamp.
UpdateAnimationTiming(*document_, timelines_, kTimingUpdateForAnimationFrame); UpdateAnimationTiming(*document_, timelines_, kTimingUpdateForAnimationFrame);
// Perform a microtask checkpoint per step 3 of // 2. Remove replaced animations for doc.
// https://drafts.csswg.org/web-animations-1/#timelines. This is to ReplaceableAnimationsMap replaceable_animations_map;
// ensure that any microtasks queued up as a result of resolving or for (auto& timeline : timelines_)
// rejecting Promise objects as part of updating timelines run their timeline->getReplaceableAnimations(&replaceable_animations_map);
// callbacks prior to dispatching animation events and generating RemoveReplacedAnimations(&replaceable_animations_map);
// the next main frame.
// 3. Perform a microtask checkpoint
// This is to ensure that any microtasks queued up as a result of resolving or
// rejecting Promise objects as part of updating timelines run their callbacks
// prior to dispatching animation events and generating the next main frame.
Microtask::PerformCheckpoint(V8PerIsolateData::MainThreadIsolate()); Microtask::PerformCheckpoint(V8PerIsolateData::MainThreadIsolate());
} }
...@@ -181,4 +189,56 @@ void DocumentAnimations::GetAnimationsTargetingTreeScope( ...@@ -181,4 +189,56 @@ void DocumentAnimations::GetAnimationsTargetingTreeScope(
} }
} }
} }
void DocumentAnimations::RemoveReplacedAnimations(
DocumentAnimations::ReplaceableAnimationsMap* replaceable_animations_map) {
HeapVector<Member<Animation>> animations_to_remove;
for (auto& elem_it : *replaceable_animations_map) {
HeapVector<Member<Animation>>* animations = elem_it.value;
// Only elements with multiple animations in the replaceable state need to
// be checked.
if (animations->size() == 1)
continue;
// By processing in decreasing order by priority, we can perform a single
// pass for discovery of replaced properties.
std::sort(animations->begin(), animations->end(), CompareAnimations);
PropertyHandleSet replaced_properties;
for (auto anim_it = animations->rbegin(); anim_it != animations->rend();
anim_it++) {
// Remaining conditions for removal:
// * has a replace state of active, and
// * for which there exists for each target property of every animation
// effect associated with animation, an animation effect associated with
// a replaceable animation with a higher composite order than animation
// that includes the same target property.
// Only active animations can be removed. We still need to go through
// the process of iterating over properties if not removable to update
// the set of properties being replaced.
bool replace = (*anim_it)->ReplaceStateActive();
PropertyHandleSet animation_properties =
To<KeyframeEffect>((*anim_it)->effect())->Model()->Properties();
for (const auto& property : animation_properties) {
auto inserted = replaced_properties.insert(property);
if (inserted.is_new_entry) {
// Top-most compositor order animation affecting this property.
replace = false;
}
}
if (replace)
animations_to_remove.push_back(*anim_it);
}
}
// The list of animations for removal is constructed in reverse composite
// ordering for efficiency. Flip the ordering to ensure that events are
// dispatched in composite order.
for (auto it = animations_to_remove.rbegin();
it != animations_to_remove.rend(); it++) {
(*it)->RemoveReplacedAnimation();
}
}
} // namespace blink } // namespace blink
...@@ -75,6 +75,11 @@ class CORE_EXPORT DocumentAnimations final ...@@ -75,6 +75,11 @@ class CORE_EXPORT DocumentAnimations final
uint64_t current_transition_generation_; uint64_t current_transition_generation_;
void Trace(Visitor*) const; void Trace(Visitor*) const;
protected:
using ReplaceableAnimationsMap =
HeapHashMap<Member<Element>, Member<HeapVector<Member<Animation>>>>;
void RemoveReplacedAnimations(ReplaceableAnimationsMap*);
private: private:
Member<Document> document_; Member<Document> document_;
HeapHashSet<WeakMember<AnimationTimeline>> timelines_; HeapHashSet<WeakMember<AnimationTimeline>> timelines_;
......
...@@ -9,8 +9,8 @@ PASS Removes an animation after updating the fill mode of another animation ...@@ -9,8 +9,8 @@ PASS Removes an animation after updating the fill mode of another animation
PASS Removes an animation after updating its fill mode PASS Removes an animation after updating its fill mode
PASS Removes an animation after updating another animation's effect to one with different timing PASS Removes an animation after updating another animation's effect to one with different timing
PASS Removes an animation after updating its effect to one with different timing PASS Removes an animation after updating its effect to one with different timing
FAIL Removes an animation after updating another animation's timeline assert_equals: expected "removed" but got "active" PASS Removes an animation after updating another animation's timeline
FAIL Removes an animation after updating its timeline assert_equals: expected "removed" but got "active" PASS Removes an animation after updating its timeline
PASS Removes an animation after updating another animation's effect's properties PASS Removes an animation after updating another animation's effect's properties
PASS Removes an animation after updating its effect's properties PASS Removes an animation after updating its effect's properties
PASS Removes an animation after updating another animation's effect to one with different properties PASS Removes an animation after updating another animation's effect to one with different properties
...@@ -36,7 +36,7 @@ PASS Does NOT remove an animation after making a redundant change to another ani ...@@ -36,7 +36,7 @@ PASS Does NOT remove an animation after making a redundant change to another ani
PASS Does NOT remove an animation after making a redundant change to its timeline PASS Does NOT remove an animation after making a redundant change to its timeline
PASS Does NOT remove an animation after making a redundant change to another animation's effect's properties PASS Does NOT remove an animation after making a redundant change to another animation's effect's properties
PASS Does NOT remove an animation after making a redundant change to its effect's properties PASS Does NOT remove an animation after making a redundant change to its effect's properties
FAIL Updates ALL timelines before checking for replacement assert_equals: expected "removed" but got "active" PASS Updates ALL timelines before checking for replacement
PASS Dispatches remove events after finish events PASS Dispatches remove events after finish events
PASS Fires remove event before requestAnimationFrame PASS Fires remove event before requestAnimationFrame
PASS Queues all remove events before running them PASS Queues all remove events before running them
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment