Bug 1459536 - Allow CSS animation timing properties to be overridden using the Web Animations API; r=boris

Differential Revision: https://phabricator.services.mozilla.com/D65098

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Brian Birtles 2020-03-04 00:36:46 +00:00
Родитель c6444a7c4f
Коммит ef42e66fc8
5 изменённых файлов: 238 добавлений и 3 удалений

Просмотреть файл

@ -45,7 +45,8 @@ class AnimationEffect : public nsISupports, public nsWrapperCache {
// AnimationEffect interface
void GetTiming(EffectTiming& aRetVal) const;
void GetComputedTimingAsDict(ComputedEffectTiming& aRetVal) const;
void UpdateTiming(const OptionalEffectTiming& aTiming, ErrorResult& aRv);
virtual void UpdateTiming(const OptionalEffectTiming& aTiming,
ErrorResult& aRv);
const TimingParams& SpecifiedTiming() const { return mTiming; }
void SetSpecifiedTiming(TimingParams&& aTiming);

Просмотреть файл

@ -154,6 +154,10 @@ struct TimingParams {
mDuration = std::move(aDuration);
Update();
}
void SetDuration(const Maybe<StickyTimeDuration>& aDuration) {
mDuration = aDuration;
Update();
}
const Maybe<StickyTimeDuration>& Duration() const { return mDuration; }
void SetDelay(const TimeDuration& aDelay) {

Просмотреть файл

@ -290,6 +290,36 @@ void CSSAnimation::UpdateTiming(SeekFlag aSeekFlag,
/////////////////////// CSSAnimationKeyframeEffect ////////////////////////
void CSSAnimationKeyframeEffect::UpdateTiming(
const OptionalEffectTiming& aTiming, ErrorResult& aRv) {
KeyframeEffect::UpdateTiming(aTiming, aRv);
if (aRv.Failed()) {
return;
}
if (mAnimation && mAnimation->AsCSSAnimation()) {
CSSAnimationProperties updatedProperties = CSSAnimationProperties::None;
if (aTiming.mDuration.WasPassed()) {
updatedProperties |= CSSAnimationProperties::Duration;
}
if (aTiming.mIterations.WasPassed()) {
updatedProperties |= CSSAnimationProperties::IterationCount;
}
if (aTiming.mDirection.WasPassed()) {
updatedProperties |= CSSAnimationProperties::Direction;
}
if (aTiming.mDelay.WasPassed()) {
updatedProperties |= CSSAnimationProperties::Delay;
}
if (aTiming.mFill.WasPassed()) {
updatedProperties |= CSSAnimationProperties::FillMode;
}
mAnimation->AsCSSAnimation()->AddOverriddenProperties(updatedProperties);
}
}
void CSSAnimationKeyframeEffect::SetKeyframes(JSContext* aContext,
JS::Handle<JSObject*> aKeyframes,
ErrorResult& aRv) {
@ -403,8 +433,27 @@ static void UpdateOldAnimationPropertiesWithNew(
// identity (and any expando properties attached to it).
if (aOld.GetEffect()) {
dom::AnimationEffect* oldEffect = aOld.GetEffect();
animationChanged = oldEffect->SpecifiedTiming() != aNewTiming;
oldEffect->SetSpecifiedTiming(std::move(aNewTiming));
// Copy across the changes that are not overridden
TimingParams updatedTiming = oldEffect->SpecifiedTiming();
if (~aOverriddenProperties & CSSAnimationProperties::Duration) {
updatedTiming.SetDuration(aNewTiming.Duration());
}
if (~aOverriddenProperties & CSSAnimationProperties::IterationCount) {
updatedTiming.SetIterations(aNewTiming.Iterations());
}
if (~aOverriddenProperties & CSSAnimationProperties::Direction) {
updatedTiming.SetDirection(aNewTiming.Direction());
}
if (~aOverriddenProperties & CSSAnimationProperties::Delay) {
updatedTiming.SetDelay(aNewTiming.Delay());
}
if (~aOverriddenProperties & CSSAnimationProperties::FillMode) {
updatedTiming.SetFill(aNewTiming.Fill());
}
animationChanged = oldEffect->SpecifiedTiming() != updatedTiming;
oldEffect->SetSpecifiedTiming(std::move(updatedTiming));
KeyframeEffect* oldKeyframeEffect = oldEffect->AsKeyframeEffect();
if (~aOverriddenProperties & CSSAnimationProperties::Keyframes &&

Просмотреть файл

@ -286,6 +286,8 @@ class CSSAnimationKeyframeEffect : public dom::KeyframeEffect {
: KeyframeEffect(aDocument, std::move(aTarget), std::move(aTiming),
aOptions) {}
void UpdateTiming(const dom::OptionalEffectTiming& aTiming,
ErrorResult& aRv) override;
void SetKeyframes(JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
ErrorResult& aRv) override;
};

Просмотреть файл

@ -0,0 +1,179 @@
<!doctype html>
<meta charset=utf-8>
<title>AnimationEffect.updateTiming() for CSS animations</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes anim-empty { }
</style>
<body>
<div id="log"></div>
<script>
"use strict";
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-empty 100s';
const animation = div.getAnimations()[0];
animation.effect.updateTiming({ duration: 2 * MS_PER_SEC });
div.style.animationDuration = '4s';
div.style.animationDelay = '6s';
getComputedStyle(div).animationDuration;
assert_equals(
animation.effect.getTiming().duration,
2 * MS_PER_SEC,
'Duration should be the value set by the API'
);
assert_equals(
animation.effect.getTiming().delay,
6 * MS_PER_SEC,
'Delay should be the value set by style'
);
}, 'AnimationEffect.updateTiming({ duration }) causes changes to the'
+ ' animation-duration to be ignored');
// The above test covers duration (with delay as a control). The remaining
// properties are:
//
// iterations - animation-iteration-count
// direction - animation-direction
// delay - animation-delay
// fill - animation-fill-mode
//
// Which we test in two batches, overriding two properties each time and using
// the remaining two properties as control values to check they are NOT
// overridden.
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-empty 100s';
const animation = div.getAnimations()[0];
animation.effect.updateTiming({ iterations: 2, direction: 'reverse' });
div.style.animationIterationCount = '4';
div.style.animationDirection = 'alternate';
div.style.animationDelay = '6s';
div.style.animationFillMode = 'both';
getComputedStyle(div).animationIterationCount;
assert_equals(
animation.effect.getTiming().iterations,
2,
'Iterations should be the value set by the API'
);
assert_equals(
animation.effect.getTiming().direction,
'reverse',
'Direction should be the value set by the API'
);
assert_equals(
animation.effect.getTiming().delay,
6 * MS_PER_SEC,
'Delay should be the value set by style'
);
assert_equals(
animation.effect.getTiming().fill,
'both',
'Fill should be the value set by style'
);
}, 'AnimationEffect.updateTiming({ iterations, direction }) causes changes to'
+ ' the animation-iteration-count and animation-direction to be ignored');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-empty 100s';
const animation = div.getAnimations()[0];
animation.effect.updateTiming({ delay: 2 * MS_PER_SEC, fill: 'both' });
div.style.animationDelay = '4s';
div.style.animationFillMode = 'forwards';
div.style.animationIterationCount = '6';
div.style.animationDirection = 'reverse';
getComputedStyle(div).animationDelay;
assert_equals(
animation.effect.getTiming().delay,
2 * MS_PER_SEC,
'Delay should be the value set by the API'
);
assert_equals(
animation.effect.getTiming().fill,
'both',
'Fill should be the value set by the API'
);
assert_equals(
animation.effect.getTiming().iterations,
6,
'Iterations should be the value set by style'
);
assert_equals(
animation.effect.getTiming().direction,
'reverse',
'Direction should be the value set by style'
);
}, 'AnimationEffect.updateTiming({ delay, fill }) causes changes to'
+ ' the animation-delay and animation-fill-mode to be ignored');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-empty 100s';
const animation = div.getAnimations()[0];
assert_throws_js(TypeError, () => {
animation.effect.updateTiming({ duration: 2 * MS_PER_SEC, iterations: -1 });
}, 'Negative iteration count should cause an error to be thrown');
div.style.animationDuration = '4s';
getComputedStyle(div).animationDuration;
assert_equals(
animation.effect.getTiming().duration,
4 * MS_PER_SEC,
'Duration should be the value set by style'
);
}, 'AnimationEffect.updateTiming() does override to changes from animation-*'
+ ' properties if there is an error');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-empty 100s';
const animation = div.getAnimations()[0];
animation.effect.updateTiming({
easing: 'steps(4)',
endDelay: 2 * MS_PER_SEC,
iterationStart: 4,
});
div.style.animationDuration = '6s';
div.style.animationTimingFunction = 'ease-in';
getComputedStyle(div).animationDuration;
assert_equals(
animation.effect.getTiming().easing,
'steps(4)',
'endDelay should be the value set by the API'
);
assert_equals(
animation.effect.getTiming().endDelay,
2 * MS_PER_SEC,
'endDelay should be the value set by the API'
);
assert_equals(
animation.effect.getTiming().iterationStart,
4,
'iterationStart should be the value set by style'
);
}, 'AnimationEffect properties that do not map to animation-* properties'
+ ' should not be changed when animation-* style is updated');
</script>
</body>