Commit d3931637 authored by Yi Gu's avatar Yi Gu Committed by Commit Bot

[ScrollTimeline] Add timeline to Element.animate()

This patch adds a timeline option to Element.animate() function based on
the recent spec change: https://github.com/w3c/csswg-drafts/issues/5013.

Change-Id: Ibf7e6f824f9e013f62da015cebdbc893255142dd
Bug: 1080720
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2220352
Commit-Queue: Yi Gu <yigu@chromium.org>
Reviewed-by: default avatarMajid Valipour <majidvp@chromium.org>
Cr-Commit-Position: refs/heads/master@{#773733}
parent 378036cd
......@@ -56,6 +56,7 @@ UnrestrictedDoubleOrKeyframeEffectOptions CoerceEffectOptions(
} // namespace
// https://drafts.csswg.org/web-animations/#dom-animatable-animate
Animation* Animatable::animate(
ScriptState* script_state,
const ScriptValue& keyframes,
......@@ -72,9 +73,28 @@ Animation* Animatable::animate(
ReportFeaturePolicyViolationsIfNecessary(*element->GetExecutionContext(),
*effect->Model());
Animation* animation = element->GetDocument().Timeline().Play(effect);
if (options.IsKeyframeAnimationOptions())
animation->setId(options.GetAsKeyframeAnimationOptions()->id());
if (!options.IsKeyframeAnimationOptions())
return element->GetDocument().Timeline().Play(effect);
Animation* animation;
AnimationTimeline* timeline =
options.GetAsKeyframeAnimationOptions()->timeline();
bool timeline_is_undefined =
!options.GetAsKeyframeAnimationOptions()->hasTimeline();
bool timeline_is_null = !timeline_is_undefined && !timeline;
if (timeline_is_undefined) {
animation = element->GetDocument().Timeline().Play(effect);
} else if (timeline_is_null) {
animation = Animation::Create(element->GetExecutionContext(), effect,
timeline, exception_state);
} else {
DCHECK(timeline);
animation = timeline->Play(effect);
}
animation->setId(options.GetAsKeyframeAnimationOptions()->id());
return animation;
}
......
......@@ -216,6 +216,16 @@ void AnimationTimeline::ScheduleServiceOnNextFrame() {
document_->View()->ScheduleAnimation();
}
Animation* AnimationTimeline::Play(AnimationEffect* child) {
Animation* animation = Animation::Create(child, this);
DCHECK(animations_.Contains(animation));
animation->play();
DCHECK(animations_needing_update_.Contains(animation));
return animation;
}
void AnimationTimeline::MarkAnimationsCompositorPending(bool source_changed) {
for (const auto& animation : animations_) {
animation->SetCompositorPending(source_changed);
......
......@@ -68,6 +68,8 @@ class CORE_EXPORT AnimationTimeline : public ScriptWrappable {
// Schedules animation timing update on next frame.
virtual void ScheduleServiceOnNextFrame();
Animation* Play(AnimationEffect*);
virtual bool NeedsAnimationTimingUpdate();
virtual bool HasAnimations() const { return !animations_.IsEmpty(); }
virtual bool HasOutdatedAnimation() const {
......
......@@ -106,16 +106,6 @@ DocumentTimeline::InitialStartTimeForAnimations() {
return base::nullopt;
}
Animation* DocumentTimeline::Play(AnimationEffect* child) {
Animation* animation = Animation::Create(child, this);
DCHECK(animations_.Contains(animation));
animation->play();
DCHECK(animations_needing_update_.Contains(animation));
return animation;
}
void DocumentTimeline::ScheduleNextService() {
DCHECK_EQ(outdated_animation_count_, 0U);
......
......@@ -40,8 +40,6 @@
namespace blink {
class Animation;
class AnimationEffect;
class DocumentTimelineOptions;
// DocumentTimeline is constructed and owned by Document, and tied to its
......@@ -71,8 +69,6 @@ class CORE_EXPORT DocumentTimeline : public AnimationTimeline {
void ScheduleNextService() override;
Animation* Play(AnimationEffect*);
bool IsActive() const override;
base::Optional<base::TimeDelta> InitialStartTimeForAnimations() override;
bool HasPendingUpdates() const {
......
......@@ -6,4 +6,5 @@
dictionary KeyframeAnimationOptions : KeyframeEffectOptions {
DOMString id = "";
AnimationTimeline? timeline;
};
<html class="reftest-wait">
<title>Scroll-linked animation with Animatable interface</title>
<link rel="help" href="https://drafts.csswg.org/scroll-animations/">
<meta name="assert" content="ScrollTimeline should work with animatable
interface">
<link rel="match" href="animation-ref.html">
<script src="/web-animations/testcommon.js"></script>
<script src="/common/reftest-wait.js"></script>
<style>
#box {
width: 100px;
height: 100px;
background-color: green;
}
#covered {
width: 100px;
height: 100px;
background-color: red;
}
#scroller {
overflow: auto;
height: 100px;
width: 100px;
will-change: transform;
/* force compositing */
}
#contents {
height: 1000px;
width: 100%;
}
</style>
<div id="box"></div>
<div id="covered"></div>
<div id="scroller">
<div id="contents"></div>
</div>
<script>
const scroller = document.getElementById('scroller');
const scroll_timeline = new ScrollTimeline({
scrollSource: scroller,
timeRange: 1000
});
const box = document.getElementById('box');
const animation = box.animate(
[
{ transform: 'translateY(0)', opacity: 1 },
{ transform: 'translateY(200px)', opacity: 0 }
], {
duration: 1000,
timeline: scroll_timeline
}
);
animation.ready.then(() => {
// Move the scroller to the halfway point.
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
scroller.scrollTop = 0.5 * maxScroll;
waitForAnimationFrames(2).then(_ => {
takeScreenshot();
});
});
</script>
\ No newline at end of file
This is a testharness.js-based test.
Found 147 tests; 144 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
Found 151 tests; 148 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
PASS Element.animate() creates an Animation object
PASS Element.animate() creates an Animation object in the relevant realm of the target element
PASS Element.animate() creates an Animation object with a KeyframeEffect
......@@ -132,6 +132,10 @@ PASS Element.animate() correctly sets the id attribute when no id is specified
PASS Element.animate() correctly sets the id attribute
PASS Element.animate() correctly sets the Animation's timeline
PASS Element.animate() correctly sets the Animation's timeline when triggered on an element in a different document
PASS Element.animate() correctly sets the Animation's timeline with no timeline parameter in KeyframeAnimationOptions.
PASS Element.animate() correctly sets the Animation's timeline with undefined timeline in KeyframeAnimationOptions.
PASS Element.animate() correctly sets the Animation's timeline with null timeline in KeyframeAnimationOptions.
PASS Element.animate() correctly sets the Animation's timeline with DocumentTimeline in KeyframeAnimationOptions.
PASS Element.animate() calls play on the Animation
PASS Element.animate() does NOT trigger a style change event
PASS animate() with pseudoElement parameter creates an Animation object
......
......@@ -202,6 +202,18 @@ async_test(t => {
}, 'Element.animate() correctly sets the Animation\'s timeline when ' +
'triggered on an element in a different document');
for (const subtest of gAnimationTimelineTests) {
test(t => {
const anim = createDiv(t).animate(null, { timeline: subtest.timeline });
assert_not_equals(anim, null,
'An animation sohuld be created');
assert_equals(anim.timeline, subtest.expectedTimeline,
'Animation timeline should be '+
subtest.expectedTimelineDescription);
}, 'Element.animate() correctly sets the Animation\'s timeline '
+ subtest.description + ' in KeyframeAnimationOptions.');
}
test(t => {
const anim = createDiv(t).animate(null, 2000);
assert_equals(anim.playState, 'running');
......
......@@ -796,3 +796,32 @@ const gInvalidKeyframeEffectOptionTests = [
{ desc: 'a variable easing', input: { easing: 'var(--x)' } },
{ desc: 'a multi-value easing', input: { easing: 'ease-in-out, ease-out' } },
];
// There is currently only ScrollTimeline that can be constructed and used here
// beyond document timeline. Given that ScrollTimeline is not stable as of yet
// it's tested in scroll-animations/animation-with-animatable-interface.html.
const gAnimationTimelineTests = [
{
expectedTimeline: document.timeline,
expectedTimelineDescription: 'document.timeline',
description: 'with no timeline parameter'
},
{
timeline: undefined,
expectedTimeline: document.timeline,
expectedTimelineDescription: 'document.timeline',
description: 'with undefined timeline'
},
{
timeline: null,
expectedTimeline: null,
expectedTimelineDescription: 'null',
description: 'with null timeline'
},
{
timeline: document.timeline,
expectedTimeline: document.timeline,
expectedTimelineDescription: 'document.timeline',
description: 'with DocumentTimeline'
},
];
\ No newline at end of file
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