Commit eadf7030 authored by Kevin Ellis's avatar Kevin Ellis Committed by Commit Bot

Implement Animation::setTimeline

Mutability of the animation timeline is behind the feature flag for
ScrollTimeline. The feature is marked as web-exposed, but with a no-op
implementation if the feature flag is not set. The rationale for this
setup is that we don't support conditionally readonly attributes in IDL.

With the patch, a number of the failing tests are now failing for
different reasons:
* Replaceable animations should consider all timelines when determining
  if an animation can be removed. Our current implementation only
  considers other animations on the same timeline when making the
  decision. Fixing these failures requires some refactoring, which is
  deferred to a followup CL.
* Resetting the timeline cancels a running CSS animation or transition.
  Some refactoring is required to ensure that we properly compute the
  cancel time as it depends on the old timeline. Again, deferring to
  a followup CL.

https://github.com/w3c/csswg-drafts/issues/5159
https://github.com/w3c/csswg-drafts/issues/5422
https://drafts.csswg.org/scroll-animations-1
https://www.chromestatus.com/feature/6752840701706240

Bug: 827626
Change-Id: I1fea09a80e8730694455c06b192cb9e068eb0c55
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2324101Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Reviewed-by: default avatarOlga Gerchikov <gerchiko@microsoft.com>
Reviewed-by: default avatarAnders Hartvoll Ruud <andruud@chromium.org>
Commit-Queue: Kevin Ellis <kevers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#805371}
parent 0074e68f
......@@ -349,6 +349,7 @@ void Animation::SetCurrentTimeInternal(double new_current_time) {
} else {
start_time_ = CalculateStartTime(new_current_time);
}
reset_current_time_on_resume_ = false;
// Preserve invariant that we can only set a start time or a hold time in the
// absence of an active timeline.
......@@ -727,6 +728,111 @@ bool Animation::Affects(const Element& element,
effect->Affects(PropertyHandle(property));
}
void Animation::setTimeline(AnimationTimeline* timeline) {
// https://drafts.csswg.org/web-animations-1/#setting-the-timeline
// Steps refined to accommodate scroll timelines.
// TODO(crbug.com/827626): Update the web-animation-1 spec.
// https://github.com/w3c/csswg-drafts/pull/5423.
// Unfortunately cannot mark the setter only as being conditionally enabled
// via a feature flag. Conditionally making the feature a no-op is nearly
// equivalent.
if (!RuntimeEnabledFeatures::ScrollTimelineEnabled())
return;
// 1. Let the old timeline be the current timeline of the animation, if any.
AnimationTimeline* old_timeline = timeline_;
// 2. If the new timeline is the same object as the old timeline, abort this
// procedure.
if (old_timeline == timeline)
return;
UpdateIfNecessary();
AnimationPlayState old_play_state = CalculateAnimationPlayState();
base::Optional<double> old_current_time = CurrentTimeInternal();
CancelAnimationOnCompositor();
// 3. Let the timeline of the animation be the new timeline.
// The Blink implementation requires additional steps to link the animation
// to the new timeline. Animations with a null timeline hang off of the
// document timeline in order to be properly included in the results for
// getAnimations calls.
if (old_timeline)
old_timeline->AnimationDetached(this);
else
document_->Timeline().AnimationDetached(this);
timeline_ = timeline;
if (timeline)
timeline->AnimationAttached(this);
else
document_->Timeline().AnimationAttached(this);
SetOutdated();
reset_current_time_on_resume_ = false;
if (timeline) {
if (!timeline->IsMonotonicallyIncreasing()) {
ApplyPendingPlaybackRate();
double boundary_time = (playback_rate_ > 0) ? 0 : EffectEnd();
switch (old_play_state) {
case kIdle:
break;
case kRunning:
case kFinished:
// A non-monotonic timeline has a fixed start time at the beginning or
// end of the timeline.
start_time_ = boundary_time;
break;
case kPaused:
if (old_current_time) {
reset_current_time_on_resume_ = true;
start_time_ = base::nullopt;
hold_time_ = old_current_time.value();
} else if (PendingInternal()) {
start_time_ = boundary_time;
}
break;
default:
NOTREACHED();
}
} else if (old_current_time && old_timeline &&
!old_timeline->IsMonotonicallyIncreasing()) {
SetCurrentTimeInternal(old_current_time.value());
}
}
// 4. If the start time of animation is resolved, make the animation’s hold
// time unresolved. This step ensures that the finished play state of the
// animation is not “sticky” but is re-evaluated based on its updated
// current time.
if (start_time_)
ResetHoldTimeAndPhase();
// 5. Run the procedure to update an animation’s finished state for animation
// with the did seek flag set to false, and the synchronously notify flag
// set to false.
UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
if (content_ && !timeline_) {
// Update the timing model to capture the phase change and cancel an active
// CSS animation or transition.
content_->Invalidate();
Update(kTimingUpdateOnDemand);
}
SetCompositorPending(false);
// Inform devtools of a potential change to the play state.
NotifyProbe();
}
base::Optional<double> Animation::CalculateStartTime(
double current_time) const {
base::Optional<double> start_time;
......@@ -801,6 +907,7 @@ void Animation::setStartTime(base::Optional<double> start_time_ms,
}
}
start_time_ = new_start_time;
reset_current_time_on_resume_ = false;
// 6. Update animation’s hold time based on the first matching condition from
// the following,
......@@ -1155,6 +1262,8 @@ void Animation::PlayInternal(AutoRewind auto_rewind,
// 4. Let has finite timeline be true if animation has an associated timeline
// that is not monotonically increasing.
bool aborted_pause = pending_pause_;
bool enable_seek =
auto_rewind == AutoRewind::kEnabled || reset_current_time_on_resume_;
bool has_pending_ready_promise = false;
base::Optional<double> seek_time;
bool has_finite_timeline =
......@@ -1186,12 +1295,16 @@ void Animation::PlayInternal(AutoRewind auto_rewind,
double effective_playback_rate = EffectivePlaybackRate();
base::Optional<double> current_time = CurrentTimeInternal();
if (effective_playback_rate > 0 && auto_rewind == AutoRewind::kEnabled &&
if (reset_current_time_on_resume_) {
current_time = base::nullopt;
reset_current_time_on_resume_ = false;
}
if (effective_playback_rate > 0 && enable_seek &&
(!current_time || current_time < 0 || current_time >= EffectEnd())) {
seek_time = 0;
} else if (effective_playback_rate < 0 &&
auto_rewind == AutoRewind::kEnabled &&
} else if (effective_playback_rate < 0 && enable_seek &&
(!current_time || current_time <= 0 ||
current_time > EffectEnd())) {
if (EffectEnd() == std::numeric_limits<double>::infinity()) {
......@@ -1731,14 +1844,14 @@ Animation::CheckCanStartAnimationOnCompositorInternal() const {
// reason to composite it. Additionally, mutating the timeline playback rate
// is a debug feature available via devtools; we don't support this on the
// compositor currently and there is no reason to do so.
if (timeline_->IsDocumentTimeline() &&
To<DocumentTimeline>(*timeline_).PlaybackRate() != 1)
if (!timeline_ || (timeline_->IsDocumentTimeline() &&
To<DocumentTimeline>(*timeline_).PlaybackRate() != 1))
reasons |= CompositorAnimations::kInvalidAnimationOrEffect;
// If the scroll source is not composited, fall back to main thread.
// TODO(crbug.com/476553): Once all ScrollNodes including uncomposited ones
// are in the compositor, the animation should be composited.
if (timeline_->IsScrollTimeline() &&
if (timeline_ && timeline_->IsScrollTimeline() &&
!CompositorAnimations::CheckUsesCompositedScrolling(
To<ScrollTimeline>(*timeline_).ResolvedScrollSource()))
reasons |= CompositorAnimations::kTimelineSourceHasInvalidCompositingState;
......@@ -1801,12 +1914,6 @@ void Animation::StartAnimationOnCompositor(
// composited and non-composited animations. The use of 'compositor' in the name
// is confusing.
void Animation::SetCompositorPending(bool effect_changed) {
// Cannot play an animation with a null timeline.
// TODO(crbug.com/827626) Revisit once timelines are mutable as there will be
// work to do if the timeline is reset.
if (!timeline_)
return;
// FIXME: KeyframeEffect could notify this directly?
if (!HasActiveAnimationsOnCompositor()) {
DestroyCompositorAnimation();
......@@ -1871,9 +1978,6 @@ bool Animation::Update(TimingUpdateReason reason) {
// time of an animation also involves:
// * Running the update an animation’s finished state procedure.
// * Queueing animation events.
if (!timeline_)
return false;
ClearOutdated();
bool idle = CalculateAnimationPlayState() == kIdle;
if (!idle)
......@@ -1940,11 +2044,6 @@ void Animation::QueueFinishedEvent() {
}
void Animation::UpdateIfNecessary() {
// Update is a no-op if there is no timeline_, and will not reset the outdated
// state in this case.
if (!timeline_)
return;
if (Outdated())
Update(kTimingUpdateOnDemand);
DCHECK(!Outdated());
......
......@@ -199,6 +199,7 @@ class CORE_EXPORT Animation : public EventTargetWithInlineData,
double playbackRate() const;
void setPlaybackRate(double, ExceptionState& = ASSERT_NO_EXCEPTION);
AnimationTimeline* timeline() { return timeline_; }
void setTimeline(AnimationTimeline* timeline);
Document* GetDocument() const;
base::Optional<double> startTime() const;
......@@ -386,6 +387,7 @@ class CORE_EXPORT Animation : public EventTargetWithInlineData,
base::Optional<double> hold_time_;
base::Optional<TimelinePhase> hold_phase_;
base::Optional<double> previous_current_time_;
bool reset_current_time_on_resume_ = false;
unsigned sequence_number_;
......
......@@ -40,8 +40,7 @@ enum ReplaceState { "active", "removed", "persisted" };
] interface Animation : EventTarget {
[CallWith=ExecutionContext, RaisesException] constructor(optional AnimationEffect? effect = null, optional AnimationTimeline? timeline);
[Measure] attribute AnimationEffect? effect;
// TODO(suzyh): Make timeline mutable.
[RuntimeEnabled=WebAnimationsAPI] readonly attribute AnimationTimeline? timeline;
[RuntimeEnabled=WebAnimationsAPI] attribute AnimationTimeline? timeline;
[Measure, RaisesException=Setter] attribute double? startTime;
[Measure, RaisesException=Setter] attribute double? currentTime;
[Measure, RaisesException=Setter] attribute double playbackRate;
......
......@@ -165,8 +165,14 @@ void PendingAnimations::NotifyCompositorAnimationStarted(
waiting_for_compositor_animation_start_.push_back(animation);
continue;
}
animation->NotifyReady(monotonic_animation_start_time -
animation->timeline()->ZeroTimeInSeconds());
if (animation->timeline() &&
!animation->timeline()->IsMonotonicallyIncreasing()) {
animation->NotifyReady(
animation->timeline()->CurrentTimeSeconds().value_or(0));
} else {
animation->NotifyReady(monotonic_animation_start_time -
animation->timeline()->ZeroTimeInSeconds());
}
}
}
......
......@@ -5,7 +5,7 @@ PASS Before -> Active
PASS Before -> After
PASS Active -> Idle, display: none
PASS After -> Idle, display: none
FAIL Active -> Idle, setting Animation.timeline = null promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL Active -> Idle, setting Animation.timeline = null assert_approx_equals: expected 0.1 +/- 0.0005 but got 0
PASS Active -> Idle, calling Animation.cancel()
PASS Active -> Before
PASS Active -> After
......@@ -24,7 +24,7 @@ PASS Redundant change, after -> active, then back
PASS Call Animation.cancel after canceling animation.
PASS Restart animation after canceling animation immediately.
PASS Call Animation.cancel after restarting animation immediately.
FAIL Set timeline and play transition after clearing the timeline. promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
PASS Set timeline and play transition after clearing the timeline.
PASS Set null target effect after canceling the animation.
PASS Cancel the animation after clearing the target effect.
Harness: the test ran to completion.
......
......@@ -4,15 +4,15 @@ PASS Idle -> Before
PASS Idle or Pending -> Active
PASS Idle or Pending -> After
PASS Before -> Idle (display: none)
FAIL Before -> Idle (Animation.timeline = null) promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
PASS Before -> Idle (Animation.timeline = null)
PASS Before -> Active
PASS Before -> After
PASS Active -> Idle, no delay (display: none)
FAIL Active -> Idle, no delay (Animation.timeline = null) promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
PASS Active -> Idle, no delay (Animation.timeline = null)
PASS Active -> Idle, with positive delay (display: none)
FAIL Active -> Idle, with positive delay (Animation.timeline = null) promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
PASS Active -> Idle, with positive delay (Animation.timeline = null)
PASS Active -> Idle, with negative delay (display: none)
FAIL Active -> Idle, with negative delay (Animation.timeline = null) promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL Active -> Idle, with negative delay (Animation.timeline = null) assert_equals: expected 0 but got 100
PASS Active -> Before
PASS Active -> After
PASS After -> Before
......@@ -22,7 +22,7 @@ FAIL Calculating the interval start and end time with negative end delay. assert
PASS Call Animation.cancel after canceling transition.
PASS Restart transition after canceling transition immediately
PASS Call Animation.cancel after restarting transition immediately
FAIL Set timeline and play transition after clear the timeline promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
PASS Set timeline and play transition after clear the timeline
PASS Set null target effect after canceling the transition
PASS Cancel the transition after clearing the target effect
PASS Cancel the transition after it finishes
......
<!DOCTYPE html>
<meta charset=utf-8>
<title>Setting the timeline of scroll animation</title>
<link rel="help"
href="https://drafts.csswg.org/web-animations-1/#setting-the-timeline">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<script src="testcommon.js"></script>
<style>
.scroller {
overflow: auto;
height: 200px;
width: 100px;
will-change: transform;
}
.contents {
/* The height is set to align scrolling in pixels with logical time in ms */
height: 1200px;
width: 100%;
}
@keyframes anim {
from { opacity: 0; }
to { opacity: 1; }
}
.anim {
animation: anim 1s paused linear;
}
</style>
<body>
<script>
'use strict';
function createAnimation(t) {
const elem = createDiv(t);
const animation = elem.animate({ opacity: [1, 0] }, 1000);
return animation;
}
function createPausedCssAnimation(t) {
const elem = createDiv(t);
elem.classList.add('anim');
return elem.getAnimations()[0];
}
function updateScrollPosition(timeline, offset) {
const scroller = timeline.scrollSource;
assert_true(!!scroller, 'scrollSource is resolved');
scroller.scrollTop = offset;
// Wait for new animation frame which allows the timeline to compute new
// current time.
return waitForNextFrame();
}
function assert_timeline_current_time(animation, timeline_current_time) {
assert_times_equal(animation.timeline.currentTime, timeline_current_time,
'Timeline\'s currentTime aligns with the scroll ' +
'position even when paused');
}
function assert_scroll_synced_times(animation, timeline_current_time,
animation_current_time) {
assert_timeline_current_time(animation, timeline_current_time);
assert_times_equal(animation.currentTime, animation_current_time,
'Animation\'s currentTime aligns with the scroll ' +
'position');
}
function assert_paused_times(animation, timeline_current_time,
animation_current_time) {
assert_timeline_current_time(animation, timeline_current_time);
assert_times_equal(animation.currentTime, animation_current_time,
'Animation\'s currentTime is fixed while paused');
}
promise_test(async t => {
const scrollTimeline = createScrollTimeline(t);
await updateScrollPosition(scrollTimeline, 100);
const animation = createAnimation(t);
animation.timeline = scrollTimeline;
assert_true(animation.pending);
await animation.ready;
assert_equals(animation.playState, 'running');
assert_scroll_synced_times(animation, 100, 100);
}, 'Setting a scroll timeline on a play-pending animation synchronizes ' +
'currentTime of the animation with the scroll position.');
promise_test(async t => {
const scrollTimeline = createScrollTimeline(t);
await updateScrollPosition(scrollTimeline, 100);
const animation = createAnimation(t);
animation.pause();
animation.timeline = scrollTimeline;
assert_true(animation.pending);
await animation.ready;
assert_equals(animation.playState, 'paused');
assert_paused_times(animation, 100, 0);
await updateScrollPosition(animation.timeline, 200);
assert_equals(animation.playState, 'paused');
assert_paused_times(animation, 200, 0);
animation.play();
await animation.ready;
assert_scroll_synced_times(animation, 200, 200);
}, 'Setting a scroll timeline on a pause-pending animation fixes the ' +
'currentTime of the animation based on the scroll position once resumed');
promise_test(async t => {
const scrollTimeline = createScrollTimeline(t);
await updateScrollPosition(scrollTimeline, 100);
const animation = createAnimation(t);
animation.reverse();
animation.timeline = scrollTimeline;
await animation.ready;
assert_equals(animation.playState, 'running');
assert_scroll_synced_times(animation, 100, 900);
}, 'Setting a scroll timeline on a reversed play-pending animation ' +
'synchronizes the currentTime of the animation with the scroll ' +
'position.');
promise_test(async t => {
const scrollTimeline = createScrollTimeline(t);
await updateScrollPosition(scrollTimeline, 100);
const animation = createAnimation(t);
await animation.ready;
animation.timeline = scrollTimeline;
assert_false(animation.pending);
assert_equals(animation.playState, 'running');
assert_scroll_synced_times(animation, 100, 100);
}, 'Setting a scroll timeline on a running animation synchronizes the ' +
'currentTime of the animation with the scroll position.');
promise_test(async t => {
const scrollTimeline = createScrollTimeline(t);
await updateScrollPosition(scrollTimeline, 100);
const animation = createAnimation(t);
animation.pause();
await animation.ready;
animation.timeline = scrollTimeline;
assert_false(animation.pending);
assert_equals(animation.playState, 'paused');
assert_paused_times(animation, 100, 0);
animation.play();
await animation.ready;
assert_scroll_synced_times(animation, 100, 100);
}, 'Setting a scroll timeline on a paused animation fixes the currentTime of ' +
'the animation based on the scroll position when resumed');
promise_test(async t => {
const scrollTimeline = createScrollTimeline(t);
await updateScrollPosition(scrollTimeline, 100);
const animation = createAnimation(t);
animation.reverse();
animation.pause();
await animation.ready;
animation.timeline = scrollTimeline;
assert_false(animation.pending);
assert_equals(animation.playState, 'paused');
assert_paused_times(animation, 100, 1000);
animation.play();
await animation.ready;
assert_scroll_synced_times(animation, 100, 900);
}, 'Setting a scroll timeline on a reversed paused animation ' +
'fixes the currentTime of the animation based on the scroll ' +
'position when resumed');
promise_test(async t => {
const animation = createAnimation(t);
const scrollTimeline = createScrollTimeline(t);
animation.timeline = scrollTimeline;
await animation.ready;
await updateScrollPosition(scrollTimeline, 100);
animation.timeline = document.timeline;
assert_times_equal(animation.currentTime, 100);
}, 'Transitioning from a scroll timeline to a document timeline on a running ' +
'animation preserves currentTime');
promise_test(async t => {
const animation = createAnimation(t);
const scrollTimeline = createScrollTimeline(t);
animation.timeline = scrollTimeline;
await animation.ready;
await updateScrollPosition(scrollTimeline, 100);
animation.pause();
animation.timeline = document.timeline;
await animation.ready;
assert_times_equal(animation.currentTime, 100);
}, 'Transitioning from a scroll timeline to a document timeline on a ' +
'pause-pending animation preserves currentTime');
promise_test(async t => {
const animation = createAnimation(t);
const scrollTimeline = createScrollTimeline(t);
animation.timeline = scrollTimeline;
await animation.ready;
await updateScrollPosition(scrollTimeline, 100);
assert_equals(animation.playState, 'running');
animation.timeline = null;
assert_equals(animation.playState, 'running');
}, 'Transitioning from a scroll timeline to a null timeline on a running ' +
'animation preserves the play state');
promise_test(async t => {
const keyframeEfect = new KeyframeEffect(createDiv(t),
{ opacity: [0, 1] },
1000);
const animation = new Animation(keyframeEfect, null);
animation.startTime = 0;
assert_equals(animation.playState, 'running');
const scrollTimeline = createScrollTimeline(t);
await updateScrollPosition(scrollTimeline, 100);
animation.timeline = scrollTimeline;
assert_equals(animation.playState, 'running');
assert_times_equal(animation.currentTime, 100);
}, 'Switching from a null timeline to a scroll timeline on an animation with ' +
'a resolved start time preserved the play state');
promise_test(async t => {
const firstScrollTimeline = createScrollTimeline(t);
await updateScrollPosition(firstScrollTimeline, 100);
const secondScrollTimeline = createScrollTimeline(t);
await updateScrollPosition(secondScrollTimeline, 200);
const animation = createAnimation(t);
animation.timeline = firstScrollTimeline;
await animation.ready;
assert_times_equal(animation.currentTime, 100);
animation.timeline = secondScrollTimeline;
assert_times_equal(animation.currentTime, 200);
}, 'Switching from one scroll timeline to another updates currentTime');
promise_test(async t => {
const scrollTimeline = createScrollTimeline(t);
await updateScrollPosition(scrollTimeline, 100);
const animation = createPausedCssAnimation(t);
animation.timeline = scrollTimeline;
await animation.ready;
assert_equals(animation.playState, 'paused');
assert_times_equal(animation.currentTime, 0);
const target = animation.effect.target;
target.style.animationPlayState = 'running';
await animation.ready;
assert_times_equal(animation.currentTime, 100);
}, 'Switching from a document timeline to a scroll timeline updates ' +
'currentTime when unpaused via CSS.');
promise_test(async t => {
const scrollTimeline = createScrollTimeline(t);
await updateScrollPosition(scrollTimeline, 100);
const animation = createAnimation(t);
animation.pause();
animation.timeline = scrollTimeline;
await animation.ready;
assert_times_equal(animation.currentTime, 0);
animation.currentTime = 500;
animation.play();
await animation.ready;
assert_times_equal(animation.currentTime, 500);
}, 'Switching from a document timeline to a scroll timeline and updating ' +
'currentTime preserves the new value when unpaused.');
promise_test(async t => {
const scrollTimeline = createScrollTimeline(t);
await updateScrollPosition(scrollTimeline, 100);
const animation = createAnimation(t);
animation.pause();
animation.timeline = scrollTimeline;
await animation.ready;
assert_times_equal(animation.currentTime, 0);
assert_equals(animation.playState, 'paused');
// Set a start time that will ensure that current time is in the active
// region in order not to trigger a seek when play is called.
animation.startTime = -100;
assert_equals(animation.playState, 'running');
animation.play();
await animation.ready;
assert_times_equal(animation.startTime, -100);
}, 'Switching from a document timeline to a scroll timeline and updating ' +
'startTime preserves the new value when play is called.');
</script>
</body>
This is a testharness.js-based test.
Found 145 tests; 144 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
PASS idl_test setup
PASS idl_test validation
PASS Partial interface Document: original interface defined
PASS Partial interface Document: member names are unique
PASS Partial interface mixin DocumentOrShadowRoot: original interface mixin defined
PASS Partial interface mixin DocumentOrShadowRoot: member names are unique
PASS Partial interface Document[2]: member names are unique
PASS Partial interface Document[3]: member names are unique
PASS Element includes Animatable: member names are unique
PASS Element includes ParentNode: member names are unique
PASS Element includes NonDocumentTypeChildNode: member names are unique
PASS Element includes ChildNode: member names are unique
PASS Element includes Slottable: member names are unique
PASS Document includes NonElementParentNode: member names are unique
PASS Document includes DocumentOrShadowRoot: member names are unique
PASS Document includes ParentNode: member names are unique
PASS Document includes XPathEvaluatorBase: member names are unique
PASS Document includes GlobalEventHandlers: member names are unique
PASS Document includes DocumentAndElementEventHandlers: member names are unique
PASS DocumentFragment includes NonElementParentNode: member names are unique
PASS DocumentFragment includes ParentNode: member names are unique
PASS ShadowRoot includes DocumentOrShadowRoot: member names are unique
PASS AnimationTimeline interface: existence and properties of interface object
PASS AnimationTimeline interface object length
PASS AnimationTimeline interface object name
PASS AnimationTimeline interface: existence and properties of interface prototype object
PASS AnimationTimeline interface: existence and properties of interface prototype object's "constructor" property
PASS AnimationTimeline interface: existence and properties of interface prototype object's @@unscopables property
PASS AnimationTimeline interface: attribute currentTime
PASS AnimationTimeline interface: attribute phase
PASS DocumentTimeline interface: existence and properties of interface object
PASS DocumentTimeline interface object length
PASS DocumentTimeline interface object name
PASS DocumentTimeline interface: existence and properties of interface prototype object
PASS DocumentTimeline interface: existence and properties of interface prototype object's "constructor" property
PASS DocumentTimeline interface: existence and properties of interface prototype object's @@unscopables property
PASS DocumentTimeline must be primary interface of document.timeline
PASS Stringification of document.timeline
PASS AnimationTimeline interface: document.timeline must inherit property "currentTime" with the proper type
PASS AnimationTimeline interface: document.timeline must inherit property "phase" with the proper type
PASS Animation interface: existence and properties of interface object
PASS Animation interface object length
PASS Animation interface object name
PASS Animation interface: existence and properties of interface prototype object
PASS Animation interface: existence and properties of interface prototype object's "constructor" property
PASS Animation interface: existence and properties of interface prototype object's @@unscopables property
PASS Animation interface: attribute id
PASS Animation interface: attribute effect
FAIL Animation interface: attribute timeline assert_equals: setter must be function for PutForwards, Replaceable, or non-readonly attributes expected "function" but got "undefined"
PASS Animation interface: attribute startTime
PASS Animation interface: attribute currentTime
PASS Animation interface: attribute playbackRate
PASS Animation interface: attribute playState
PASS Animation interface: attribute replaceState
PASS Animation interface: attribute pending
PASS Animation interface: attribute ready
PASS Animation interface: attribute finished
PASS Animation interface: attribute onfinish
PASS Animation interface: attribute oncancel
PASS Animation interface: attribute onremove
PASS Animation interface: operation cancel()
PASS Animation interface: operation finish()
PASS Animation interface: operation play()
PASS Animation interface: operation pause()
PASS Animation interface: operation updatePlaybackRate(double)
PASS Animation interface: operation reverse()
PASS Animation interface: operation persist()
PASS Animation interface: operation commitStyles()
PASS Animation must be primary interface of new Animation()
PASS Stringification of new Animation()
PASS Animation interface: new Animation() must inherit property "id" with the proper type
PASS Animation interface: new Animation() must inherit property "effect" with the proper type
PASS Animation interface: new Animation() must inherit property "timeline" with the proper type
PASS Animation interface: new Animation() must inherit property "startTime" with the proper type
PASS Animation interface: new Animation() must inherit property "currentTime" with the proper type
PASS Animation interface: new Animation() must inherit property "playbackRate" with the proper type
PASS Animation interface: new Animation() must inherit property "playState" with the proper type
PASS Animation interface: new Animation() must inherit property "replaceState" with the proper type
PASS Animation interface: new Animation() must inherit property "pending" with the proper type
PASS Animation interface: new Animation() must inherit property "ready" with the proper type
PASS Animation interface: new Animation() must inherit property "finished" with the proper type
PASS Animation interface: new Animation() must inherit property "onfinish" with the proper type
PASS Animation interface: new Animation() must inherit property "oncancel" with the proper type
PASS Animation interface: new Animation() must inherit property "onremove" with the proper type
PASS Animation interface: new Animation() must inherit property "cancel()" with the proper type
PASS Animation interface: new Animation() must inherit property "finish()" with the proper type
PASS Animation interface: new Animation() must inherit property "play()" with the proper type
PASS Animation interface: new Animation() must inherit property "pause()" with the proper type
PASS Animation interface: new Animation() must inherit property "updatePlaybackRate(double)" with the proper type
PASS Animation interface: calling updatePlaybackRate(double) on new Animation() with too few arguments must throw TypeError
PASS Animation interface: new Animation() must inherit property "reverse()" with the proper type
PASS Animation interface: new Animation() must inherit property "persist()" with the proper type
PASS Animation interface: new Animation() must inherit property "commitStyles()" with the proper type
PASS AnimationEffect interface: existence and properties of interface object
PASS AnimationEffect interface object length
PASS AnimationEffect interface object name
PASS AnimationEffect interface: existence and properties of interface prototype object
PASS AnimationEffect interface: existence and properties of interface prototype object's "constructor" property
PASS AnimationEffect interface: existence and properties of interface prototype object's @@unscopables property
PASS AnimationEffect interface: operation getTiming()
PASS AnimationEffect interface: operation getComputedTiming()
PASS AnimationEffect interface: operation updateTiming(optional OptionalEffectTiming)
PASS KeyframeEffect interface: existence and properties of interface object
PASS KeyframeEffect interface object length
PASS KeyframeEffect interface object name
PASS KeyframeEffect interface: existence and properties of interface prototype object
PASS KeyframeEffect interface: existence and properties of interface prototype object's "constructor" property
PASS KeyframeEffect interface: existence and properties of interface prototype object's @@unscopables property
PASS KeyframeEffect interface: attribute target
PASS KeyframeEffect interface: attribute pseudoElement
PASS KeyframeEffect interface: attribute composite
PASS KeyframeEffect interface: operation getKeyframes()
PASS KeyframeEffect interface: operation setKeyframes(object?)
PASS KeyframeEffect must be primary interface of new KeyframeEffect(null, null)
PASS Stringification of new KeyframeEffect(null, null)
PASS KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "target" with the proper type
PASS KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "pseudoElement" with the proper type
PASS KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "composite" with the proper type
PASS KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "getKeyframes()" with the proper type
PASS KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "setKeyframes(object?)" with the proper type
PASS KeyframeEffect interface: calling setKeyframes(object?) on new KeyframeEffect(null, null) with too few arguments must throw TypeError
PASS AnimationEffect interface: new KeyframeEffect(null, null) must inherit property "getTiming()" with the proper type
PASS AnimationEffect interface: new KeyframeEffect(null, null) must inherit property "getComputedTiming()" with the proper type
PASS AnimationEffect interface: new KeyframeEffect(null, null) must inherit property "updateTiming(optional OptionalEffectTiming)" with the proper type
PASS AnimationEffect interface: calling updateTiming(optional OptionalEffectTiming) on new KeyframeEffect(null, null) with too few arguments must throw TypeError
PASS AnimationPlaybackEvent interface: existence and properties of interface object
PASS AnimationPlaybackEvent interface object length
PASS AnimationPlaybackEvent interface object name
PASS AnimationPlaybackEvent interface: existence and properties of interface prototype object
PASS AnimationPlaybackEvent interface: existence and properties of interface prototype object's "constructor" property
PASS AnimationPlaybackEvent interface: existence and properties of interface prototype object's @@unscopables property
PASS AnimationPlaybackEvent interface: attribute currentTime
PASS AnimationPlaybackEvent interface: attribute timelineTime
PASS AnimationPlaybackEvent must be primary interface of new AnimationPlaybackEvent("cancel")
PASS Stringification of new AnimationPlaybackEvent("cancel")
PASS AnimationPlaybackEvent interface: new AnimationPlaybackEvent("cancel") must inherit property "currentTime" with the proper type
PASS AnimationPlaybackEvent interface: new AnimationPlaybackEvent("cancel") must inherit property "timelineTime" with the proper type
PASS Document interface: attribute timeline
PASS Document interface: operation getAnimations()
PASS Document interface: document must inherit property "timeline" with the proper type
PASS Document interface: document must inherit property "getAnimations()" with the proper type
PASS ShadowRoot interface: operation getAnimations()
PASS ShadowRoot interface: shadowRoot must inherit property "getAnimations()" with the proper type
PASS Element interface: operation animate(object?, optional (unrestricted double or KeyframeAnimationOptions))
PASS Element interface: operation getAnimations(optional GetAnimationsOptions)
Harness: the test ran to completion.
This is a testharness.js-based test.
PASS Element.animate() creates an animation with the correct timeline when called on an element in a document without a browsing context
PASS The timeline associated with an animation trigger on an element in a document without a browsing context is inactive
FAIL Replacing the timeline of an animation targetting an element in a document without a browsing context leaves it in the pending state promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL Replacing the timeline of an animation targetting an element in a document without a browsing context and then adopting that element causes it to start updating style promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL Replacing the timeline of an animation targetting an element in a document without a browsing context leaves it in the pending state assert_true: The animation should still be pending after replacing the document timeline expected true got false
PASS Replacing the timeline of an animation targetting an element in a document without a browsing context and then adopting that element causes it to start updating style
Harness: the test ran to completion.
This is a testharness.js-based test.
PASS All property keys are recognized
PASS Animation.effect produces expected style change events
PASS Animation.startTime produces expected style change events
PASS Animation.currentTime produces expected style change events
PASS Animation.playbackRate produces expected style change events
PASS Animation.playState produces expected style change events
PASS Animation.pending produces expected style change events
PASS Animation.id produces expected style change events
PASS Animation.onfinish produces expected style change events
PASS Animation.oncancel produces expected style change events
PASS Animation.cancel produces expected style change events
PASS Animation.finish produces expected style change events
PASS Animation.pause produces expected style change events
PASS Animation.play produces expected style change events
PASS Animation.reverse produces expected style change events
PASS Animation.updatePlaybackRate produces expected style change events
FAIL Animation.timeline produces expected style change events promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
PASS Animation.replaceState produces expected style change events
PASS Animation.onremove produces expected style change events
PASS Animation.finished produces expected style change events
PASS Animation.ready produces expected style change events
PASS Animation.commitStyles produces expected style change events
PASS Animation.persist produces expected style change events
PASS Animation.Animation constructor produces expected style change events
Harness: the test ran to completion.
This is a testharness.js-based test.
FAIL After setting timeline on paused animation it is still paused Cannot set property timeline of #<Animation> which has only a getter
FAIL After setting timeline on animation paused outside active interval it is still paused Cannot set property timeline of #<Animation> which has only a getter
FAIL After setting timeline on an idle animation without a start time it is still idle Cannot set property timeline of #<Animation> which has only a getter
FAIL After setting timeline on an idle animation with a start time it is running Cannot set property timeline of #<Animation> which has only a getter
FAIL After setting timeline on an idle animation with a sufficiently ancient start time it is finished Cannot set property timeline of #<Animation> which has only a getter
FAIL After setting timeline on a play-pending animation it begins playing after pending promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL After setting timeline on a pause-pending animation it becomes paused after pending promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL After clearing timeline on paused animation it is still paused Cannot set property timeline of #<Animation> which has only a getter
FAIL After clearing timeline on finished animation it is idle Cannot set property timeline of #<Animation> which has only a getter
FAIL After clearing timeline on running animation it is idle Cannot set property timeline of #<Animation> which has only a getter
FAIL After clearing timeline on idle animation it is still idle Cannot set property timeline of #<Animation> which has only a getter
FAIL After clearing timeline on play-pending animation it is still pending Cannot set property timeline of #<Animation> which has only a getter
FAIL After clearing and re-setting timeline on play-pending animation it begins to play promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL After clearing timeline on a pause-pending animation it is still pending Cannot set property timeline of #<Animation> which has only a getter
FAIL After clearing and re-setting timeline on a pause-pending animation it completes pausing promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL After clearing and re-setting timeline on an animation in the middle of an aborted pause, it continues playing using the same start time promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
Harness: the test ran to completion.
......@@ -65,8 +65,8 @@ test(t => {
animation.timeline = document.timeline;
assert_equals(animation.playState, 'running');
}, 'After setting timeline on an idle animation with a start time'
+ ' it is running');
}, 'After transitioning from a null timeline on an animation with a start time'
+ ' it is still running');
test(t => {
const animation =
......@@ -78,8 +78,8 @@ test(t => {
animation.timeline = document.timeline;
assert_equals(animation.playState, 'finished');
}, 'After setting timeline on an idle animation with a sufficiently ancient'
+ ' start time it is finished');
}, 'After transitioning from a null timeline on an animation with a ' +
'sufficiently ancient start time it is finished');
promise_test(async t => {
const animation =
......@@ -152,9 +152,9 @@ test(t => {
animation.timeline = null;
assert_equals(animation.playState, 'idle');
assert_equals(animation.playState, 'running');
assert_times_equal(animation.startTime, initialStartTime);
}, 'After clearing timeline on finished animation it is idle');
}, 'After clearing timeline on finished animation it is running');
test(t => {
const animation =
......@@ -166,9 +166,9 @@ test(t => {
animation.timeline = null;
assert_equals(animation.playState, 'idle');
assert_equals(animation.playState, 'running');
assert_times_equal(animation.startTime, initialStartTime);
}, 'After clearing timeline on running animation it is idle');
}, 'After clearing timeline on running animation it is still running');
test(t => {
const 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 another animation's 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 promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL Removes an animation after updating its timeline promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL Removes an animation after updating another animation's timeline assert_equals: expected "removed" but got "active"
FAIL Removes an animation after updating its timeline assert_equals: expected "removed" but got "active"
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 another animation's effect to one with different properties
......@@ -32,11 +32,11 @@ PASS Dispatches an event when removing
PASS Does NOT dispatch a remove event twice
PASS Does NOT remove an animation after making a redundant change to another animation's current time
PASS Does NOT remove an animation after making a redundant change to its current time
FAIL Does NOT remove an animation after making a redundant change to another animation's timeline promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL Does NOT remove an animation after making a redundant change to its timeline promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
PASS Does NOT remove an animation after making a redundant change to another animation's 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 its effect's properties
FAIL Updates ALL timelines before checking for replacement promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
FAIL Updates ALL timelines before checking for replacement assert_equals: expected "removed" but got "active"
PASS Dispatches remove events after finish events
PASS Fires remove event before requestAnimationFrame
PASS Queues all remove events before running them
......
......@@ -72,6 +72,7 @@ interface Animation : EventTarget
setter onremove
setter playbackRate
setter startTime
setter timeline
interface AnimationEffect
attribute @@toStringTag
method constructor
......
......@@ -197,6 +197,7 @@ interface Animation : EventTarget
setter onremove
setter playbackRate
setter startTime
setter timeline
interface AnimationEffect
attribute @@toStringTag
method constructor
......
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