Line data Source code
1 : /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "nsAnimationManager.h"
7 : #include "nsTransitionManager.h"
8 : #include "mozilla/dom/CSSAnimationBinding.h"
9 :
10 : #include "mozilla/AnimationTarget.h"
11 : #include "mozilla/EffectCompositor.h"
12 : #include "mozilla/EffectSet.h"
13 : #include "mozilla/MemoryReporting.h"
14 : #include "mozilla/ServoStyleSet.h"
15 : #include "mozilla/StyleAnimationValue.h"
16 : #include "mozilla/dom/DocumentTimeline.h"
17 : #include "mozilla/dom/KeyframeEffectReadOnly.h"
18 :
19 : #include "nsPresContext.h"
20 : #include "nsStyleSet.h"
21 : #include "nsStyleChangeList.h"
22 : #include "nsContentUtils.h"
23 : #include "nsCSSRules.h"
24 : #include "mozilla/GeckoRestyleManager.h"
25 : #include "nsLayoutUtils.h"
26 : #include "nsIFrame.h"
27 : #include "nsIDocument.h"
28 : #include "nsDOMMutationObserver.h"
29 : #include "nsIPresShell.h"
30 : #include "nsIPresShellInlines.h"
31 : #include <algorithm> // std::stable_sort
32 : #include <math.h>
33 :
34 : using namespace mozilla;
35 : using namespace mozilla::css;
36 : using mozilla::dom::Animation;
37 : using mozilla::dom::AnimationPlayState;
38 : using mozilla::dom::KeyframeEffectReadOnly;
39 : using mozilla::dom::CSSAnimation;
40 :
41 : typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
42 :
43 : namespace {
44 :
45 : struct AnimationEventParams {
46 : EventMessage mMessage;
47 : StickyTimeDuration mElapsedTime;
48 : TimeStamp mTimeStamp;
49 : };
50 :
51 : } // anonymous namespace
52 :
53 : ////////////////////////// CSSAnimation ////////////////////////////
54 :
55 : JSObject*
56 0 : CSSAnimation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
57 : {
58 0 : return dom::CSSAnimationBinding::Wrap(aCx, this, aGivenProto);
59 : }
60 :
61 : mozilla::dom::Promise*
62 0 : CSSAnimation::GetReady(ErrorResult& aRv)
63 : {
64 0 : FlushStyle();
65 0 : return Animation::GetReady(aRv);
66 : }
67 :
68 : void
69 0 : CSSAnimation::Play(ErrorResult &aRv, LimitBehavior aLimitBehavior)
70 : {
71 0 : mPauseShouldStick = false;
72 0 : Animation::Play(aRv, aLimitBehavior);
73 0 : }
74 :
75 : void
76 0 : CSSAnimation::Pause(ErrorResult& aRv)
77 : {
78 0 : mPauseShouldStick = true;
79 0 : Animation::Pause(aRv);
80 0 : }
81 :
82 : AnimationPlayState
83 0 : CSSAnimation::PlayStateFromJS() const
84 : {
85 : // Flush style to ensure that any properties controlling animation state
86 : // (e.g. animation-play-state) are fully updated.
87 0 : FlushStyle();
88 0 : return Animation::PlayStateFromJS();
89 : }
90 :
91 : void
92 0 : CSSAnimation::PlayFromJS(ErrorResult& aRv)
93 : {
94 : // Note that flushing style below might trigger calls to
95 : // PlayFromStyle()/PauseFromStyle() on this object.
96 0 : FlushStyle();
97 0 : Animation::PlayFromJS(aRv);
98 0 : }
99 :
100 : void
101 0 : CSSAnimation::PlayFromStyle()
102 : {
103 0 : mIsStylePaused = false;
104 0 : if (!mPauseShouldStick) {
105 0 : ErrorResult rv;
106 0 : PlayNoUpdate(rv, Animation::LimitBehavior::Continue);
107 : // play() should not throw when LimitBehavior is Continue
108 0 : MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing animation");
109 : }
110 0 : }
111 :
112 : void
113 0 : CSSAnimation::PauseFromStyle()
114 : {
115 : // Check if the pause state is being overridden
116 0 : if (mIsStylePaused) {
117 0 : return;
118 : }
119 :
120 0 : mIsStylePaused = true;
121 0 : ErrorResult rv;
122 0 : PauseNoUpdate(rv);
123 : // pause() should only throw when *all* of the following conditions are true:
124 : // - we are in the idle state, and
125 : // - we have a negative playback rate, and
126 : // - we have an infinitely repeating animation
127 : // The first two conditions will never happen under regular style processing
128 : // but could happen if an author made modifications to the Animation object
129 : // and then updated animation-play-state. It's an unusual case and there's
130 : // no obvious way to pass on the exception information so we just silently
131 : // fail for now.
132 0 : if (rv.Failed()) {
133 0 : NS_WARNING("Unexpected exception pausing animation - silently failing");
134 : }
135 : }
136 :
137 : void
138 0 : CSSAnimation::Tick()
139 : {
140 0 : Animation::Tick();
141 0 : QueueEvents();
142 0 : }
143 :
144 : bool
145 0 : CSSAnimation::HasLowerCompositeOrderThan(const CSSAnimation& aOther) const
146 : {
147 0 : MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
148 : "Should only be called for CSS animations that are sorted "
149 : "as CSS animations (i.e. tied to CSS markup)");
150 :
151 : // 0. Object-equality case
152 0 : if (&aOther == this) {
153 0 : return false;
154 : }
155 :
156 : // 1. Sort by document order
157 0 : if (!mOwningElement.Equals(aOther.mOwningElement)) {
158 0 : return mOwningElement.LessThan(aOther.mOwningElement);
159 : }
160 :
161 : // 2. (Same element and pseudo): Sort by position in animation-name
162 0 : return mAnimationIndex < aOther.mAnimationIndex;
163 : }
164 :
165 : void
166 0 : CSSAnimation::QueueEvents(StickyTimeDuration aActiveTime)
167 : {
168 : // If the animation is pending, we ignore animation events until we finish
169 : // pending.
170 0 : if (mPendingState != PendingState::NotPending) {
171 0 : return;
172 : }
173 :
174 : // CSS animations dispatch events at their owning element. This allows
175 : // script to repurpose a CSS animation to target a different element,
176 : // to use a group effect (which has no obvious "target element"), or
177 : // to remove the animation effect altogether whilst still getting
178 : // animation events.
179 : //
180 : // It does mean, however, that for a CSS animation that has no owning
181 : // element (e.g. it was created using the CSSAnimation constructor or
182 : // disassociated from CSS) no events are fired. If it becomes desirable
183 : // for these animations to still fire events we should spec the concept
184 : // of the "original owning element" or "event target" and allow script
185 : // to set it when creating a CSSAnimation object.
186 0 : if (!mOwningElement.IsSet()) {
187 0 : return;
188 : }
189 :
190 : dom::Element* owningElement;
191 : CSSPseudoElementType owningPseudoType;
192 0 : mOwningElement.GetElement(owningElement, owningPseudoType);
193 0 : MOZ_ASSERT(owningElement, "Owning element should be set");
194 :
195 : // Get the nsAnimationManager so we can queue events on it
196 : nsPresContext* presContext =
197 0 : nsContentUtils::GetContextForContent(owningElement);
198 0 : if (!presContext) {
199 0 : return;
200 : }
201 0 : nsAnimationManager* manager = presContext->AnimationManager();
202 :
203 0 : const StickyTimeDuration zeroDuration;
204 0 : uint64_t currentIteration = 0;
205 : ComputedTiming::AnimationPhase currentPhase;
206 0 : StickyTimeDuration intervalStartTime;
207 0 : StickyTimeDuration intervalEndTime;
208 0 : StickyTimeDuration iterationStartTime;
209 :
210 0 : if (!mEffect) {
211 : currentPhase = GetAnimationPhaseWithoutEffect
212 0 : <ComputedTiming::AnimationPhase>(*this);
213 : } else {
214 0 : ComputedTiming computedTiming = mEffect->GetComputedTiming();
215 0 : currentPhase = computedTiming.mPhase;
216 0 : currentIteration = computedTiming.mCurrentIteration;
217 0 : if (currentPhase == mPreviousPhase &&
218 0 : currentIteration == mPreviousIteration) {
219 0 : return;
220 : }
221 0 : intervalStartTime =
222 0 : std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().Delay()),
223 : computedTiming.mActiveDuration),
224 0 : zeroDuration);
225 0 : intervalEndTime =
226 0 : std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().Delay()),
227 : computedTiming.mActiveDuration),
228 0 : zeroDuration);
229 :
230 0 : uint64_t iterationBoundary = mPreviousIteration > currentIteration
231 0 : ? currentIteration + 1
232 0 : : currentIteration;
233 : iterationStartTime =
234 : computedTiming.mDuration.MultDouble(
235 0 : (iterationBoundary - computedTiming.mIterationStart));
236 : }
237 :
238 0 : TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
239 0 : TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
240 0 : TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime);
241 :
242 0 : AutoTArray<AnimationEventParams, 2> events;
243 :
244 : // Handle cancel event first
245 0 : if ((mPreviousPhase != AnimationPhase::Idle &&
246 0 : mPreviousPhase != AnimationPhase::After) &&
247 : currentPhase == AnimationPhase::Idle) {
248 0 : TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
249 0 : events.AppendElement(AnimationEventParams{ eAnimationCancel,
250 : aActiveTime,
251 0 : activeTimeStamp });
252 : }
253 :
254 0 : switch (mPreviousPhase) {
255 : case AnimationPhase::Idle:
256 : case AnimationPhase::Before:
257 0 : if (currentPhase == AnimationPhase::Active) {
258 0 : events.AppendElement(AnimationEventParams{ eAnimationStart,
259 : intervalStartTime,
260 0 : startTimeStamp });
261 0 : } else if (currentPhase == AnimationPhase::After) {
262 0 : events.AppendElement(AnimationEventParams{ eAnimationStart,
263 : intervalStartTime,
264 0 : startTimeStamp });
265 0 : events.AppendElement(AnimationEventParams{ eAnimationEnd,
266 : intervalEndTime,
267 0 : endTimeStamp });
268 : }
269 0 : break;
270 : case AnimationPhase::Active:
271 0 : if (currentPhase == AnimationPhase::Before) {
272 0 : events.AppendElement(AnimationEventParams{ eAnimationEnd,
273 : intervalStartTime,
274 0 : startTimeStamp });
275 0 : } else if (currentPhase == AnimationPhase::Active) {
276 : // The currentIteration must have changed or element we would have
277 : // returned early above.
278 0 : MOZ_ASSERT(currentIteration != mPreviousIteration);
279 0 : events.AppendElement(AnimationEventParams{ eAnimationIteration,
280 : iterationStartTime,
281 0 : iterationTimeStamp });
282 0 : } else if (currentPhase == AnimationPhase::After) {
283 0 : events.AppendElement(AnimationEventParams{ eAnimationEnd,
284 : intervalEndTime,
285 0 : endTimeStamp });
286 : }
287 0 : break;
288 : case AnimationPhase::After:
289 0 : if (currentPhase == AnimationPhase::Before) {
290 0 : events.AppendElement(AnimationEventParams{ eAnimationStart,
291 : intervalEndTime,
292 0 : startTimeStamp});
293 0 : events.AppendElement(AnimationEventParams{ eAnimationEnd,
294 : intervalStartTime,
295 0 : endTimeStamp });
296 0 : } else if (currentPhase == AnimationPhase::Active) {
297 0 : events.AppendElement(AnimationEventParams{ eAnimationStart,
298 : intervalEndTime,
299 0 : endTimeStamp });
300 : }
301 0 : break;
302 : }
303 0 : mPreviousPhase = currentPhase;
304 0 : mPreviousIteration = currentIteration;
305 :
306 0 : for (const AnimationEventParams& event : events){
307 : manager->QueueEvent(
308 0 : AnimationEventInfo(owningElement, owningPseudoType,
309 0 : event.mMessage, mAnimationName,
310 : event.mElapsedTime, event.mTimeStamp,
311 0 : this));
312 : }
313 : }
314 :
315 : void
316 0 : CSSAnimation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
317 : {
318 0 : if (mNeedsNewAnimationIndexWhenRun &&
319 0 : PlayState() != AnimationPlayState::Idle) {
320 0 : mAnimationIndex = sNextAnimationIndex++;
321 0 : mNeedsNewAnimationIndexWhenRun = false;
322 : }
323 :
324 0 : Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
325 0 : }
326 :
327 : ////////////////////////// nsAnimationManager ////////////////////////////
328 :
329 0 : NS_IMPL_CYCLE_COLLECTION(nsAnimationManager, mEventDispatcher)
330 :
331 0 : NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsAnimationManager, AddRef)
332 0 : NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsAnimationManager, Release)
333 :
334 : // Find the matching animation by |aName| in the old list
335 : // of animations and remove the matched animation from the list.
336 : static already_AddRefed<CSSAnimation>
337 0 : PopExistingAnimation(const nsAString& aName,
338 : nsAnimationManager::CSSAnimationCollection* aCollection)
339 : {
340 0 : if (!aCollection) {
341 0 : return nullptr;
342 : }
343 :
344 : // Animations are stored in reverse order to how they appear in the
345 : // animation-name property. However, we want to match animations beginning
346 : // from the end of the animation-name list, so we iterate *forwards*
347 : // through the collection.
348 0 : for (size_t idx = 0, length = aCollection->mAnimations.Length();
349 0 : idx != length; ++ idx) {
350 0 : CSSAnimation* cssAnim = aCollection->mAnimations[idx];
351 0 : if (cssAnim->AnimationName() == aName) {
352 0 : RefPtr<CSSAnimation> match = cssAnim;
353 0 : aCollection->mAnimations.RemoveElementAt(idx);
354 0 : return match.forget();
355 : }
356 : }
357 :
358 0 : return nullptr;
359 : }
360 :
361 1629 : class ResolvedStyleCache {
362 : public:
363 1629 : ResolvedStyleCache() : mCache() {}
364 : nsStyleContext* Get(nsPresContext *aPresContext,
365 : nsStyleContext *aParentStyleContext,
366 : Declaration* aKeyframeDeclaration);
367 :
368 : private:
369 : nsRefPtrHashtable<nsPtrHashKey<Declaration>, nsStyleContext> mCache;
370 : };
371 :
372 : nsStyleContext*
373 0 : ResolvedStyleCache::Get(nsPresContext *aPresContext,
374 : nsStyleContext *aParentStyleContext,
375 : Declaration* aKeyframeDeclaration)
376 : {
377 : // FIXME (spec): The css3-animations spec isn't very clear about how
378 : // properties are resolved when they have values that depend on other
379 : // properties (e.g., values in 'em'). I presume that they're resolved
380 : // relative to the other styles of the element. The question is
381 : // whether they are resolved relative to other animations: I assume
382 : // that they're not, since that would prevent us from caching a lot of
383 : // data that we'd really like to cache (in particular, the
384 : // StyleAnimationValue values in AnimationPropertySegment).
385 0 : nsStyleContext *result = mCache.GetWeak(aKeyframeDeclaration);
386 0 : if (!result) {
387 0 : aKeyframeDeclaration->SetImmutable();
388 : // The spec says that !important declarations should just be ignored
389 0 : MOZ_ASSERT(!aKeyframeDeclaration->HasImportantData(),
390 : "Keyframe rule has !important data");
391 :
392 0 : nsCOMArray<nsIStyleRule> rules;
393 0 : rules.AppendObject(aKeyframeDeclaration);
394 0 : MOZ_ASSERT(aPresContext->StyleSet()->IsGecko(),
395 : "ServoStyleSet should not use nsAnimationManager for "
396 : "animations");
397 0 : RefPtr<nsStyleContext> resultStrong = aPresContext->StyleSet()->AsGecko()->
398 0 : ResolveStyleByAddingRules(aParentStyleContext, rules);
399 0 : mCache.Put(aKeyframeDeclaration, resultStrong);
400 0 : result = resultStrong;
401 : }
402 0 : return result;
403 : }
404 :
405 : class MOZ_STACK_CLASS ServoCSSAnimationBuilder final {
406 : public:
407 0 : explicit ServoCSSAnimationBuilder(const ServoComputedValues* aComputedValues)
408 0 : : mComputedValues(aComputedValues)
409 : {
410 0 : MOZ_ASSERT(aComputedValues);
411 0 : }
412 :
413 0 : bool BuildKeyframes(nsPresContext* aPresContext,
414 : const StyleAnimation& aSrc,
415 : nsTArray<Keyframe>& aKeyframes)
416 : {
417 0 : ServoStyleSet* styleSet = aPresContext->StyleSet()->AsServo();
418 0 : MOZ_ASSERT(styleSet);
419 0 : const nsTimingFunction& timingFunction = aSrc.GetTimingFunction();
420 0 : return styleSet->GetKeyframesForName(aSrc.GetName(),
421 : timingFunction,
422 0 : aKeyframes);
423 : }
424 0 : void SetKeyframes(KeyframeEffectReadOnly& aEffect,
425 : nsTArray<Keyframe>&& aKeyframes)
426 : {
427 0 : aEffect.SetKeyframes(Move(aKeyframes), mComputedValues);
428 0 : }
429 :
430 : private:
431 : const ServoComputedValues* mComputedValues;
432 : };
433 :
434 1629 : class MOZ_STACK_CLASS GeckoCSSAnimationBuilder final {
435 : public:
436 1629 : GeckoCSSAnimationBuilder(nsStyleContext* aStyleContext,
437 : const NonOwningAnimationTarget& aTarget)
438 1629 : : mStyleContext(aStyleContext)
439 1629 : , mTarget(aTarget)
440 : {
441 1629 : MOZ_ASSERT(aStyleContext);
442 1629 : MOZ_ASSERT(aTarget.mElement);
443 1629 : }
444 :
445 : bool BuildKeyframes(nsPresContext* aPresContext,
446 : const StyleAnimation& aSrc,
447 : nsTArray<Keyframe>& aKeyframs);
448 0 : void SetKeyframes(KeyframeEffectReadOnly& aEffect,
449 : nsTArray<Keyframe>&& aKeyframes)
450 : {
451 0 : aEffect.SetKeyframes(Move(aKeyframes), mStyleContext);
452 0 : }
453 :
454 : private:
455 : nsTArray<Keyframe> BuildAnimationFrames(nsPresContext* aPresContext,
456 : const StyleAnimation& aSrc,
457 : const nsCSSKeyframesRule* aRule);
458 : Maybe<ComputedTimingFunction> GetKeyframeTimingFunction(
459 : nsPresContext* aPresContext,
460 : nsCSSKeyframeRule* aKeyframeRule,
461 : const Maybe<ComputedTimingFunction>& aInheritedTimingFunction);
462 : nsTArray<PropertyValuePair> GetKeyframePropertyValues(
463 : nsPresContext* aPresContext,
464 : nsCSSKeyframeRule* aKeyframeRule,
465 : nsCSSPropertyIDSet& aAnimatedProperties);
466 : void FillInMissingKeyframeValues(
467 : nsCSSPropertyIDSet aAnimatedProperties,
468 : nsCSSPropertyIDSet aPropertiesSetAtStart,
469 : nsCSSPropertyIDSet aPropertiesSetAtEnd,
470 : const Maybe<ComputedTimingFunction>& aInheritedTimingFunction,
471 : nsTArray<Keyframe>& aKeyframes);
472 :
473 : RefPtr<nsStyleContext> mStyleContext;
474 : NonOwningAnimationTarget mTarget;
475 :
476 : ResolvedStyleCache mResolvedStyles;
477 : };
478 :
479 : static Maybe<ComputedTimingFunction>
480 : ConvertTimingFunction(const nsTimingFunction& aTimingFunction);
481 :
482 : template<class BuilderType>
483 : static void
484 0 : UpdateOldAnimationPropertiesWithNew(
485 : CSSAnimation& aOld,
486 : TimingParams& aNewTiming,
487 : nsTArray<Keyframe>&& aNewKeyframes,
488 : bool aNewIsStylePaused,
489 : BuilderType& aBuilder)
490 : {
491 0 : bool animationChanged = false;
492 :
493 : // Update the old from the new so we can keep the original object
494 : // identity (and any expando properties attached to it).
495 0 : if (aOld.GetEffect()) {
496 0 : dom::AnimationEffectReadOnly* oldEffect = aOld.GetEffect();
497 0 : animationChanged = oldEffect->SpecifiedTiming() != aNewTiming;
498 0 : oldEffect->SetSpecifiedTiming(aNewTiming);
499 :
500 0 : KeyframeEffectReadOnly* oldKeyframeEffect = oldEffect->AsKeyframeEffect();
501 0 : if (oldKeyframeEffect) {
502 0 : aBuilder.SetKeyframes(*oldKeyframeEffect, Move(aNewKeyframes));
503 : }
504 : }
505 :
506 : // Handle changes in play state. If the animation is idle, however,
507 : // changes to animation-play-state should *not* restart it.
508 0 : if (aOld.PlayState() != AnimationPlayState::Idle) {
509 : // CSSAnimation takes care of override behavior so that,
510 : // for example, if the author has called pause(), that will
511 : // override the animation-play-state.
512 : // (We should check aNew->IsStylePaused() but that requires
513 : // downcasting to CSSAnimation and we happen to know that
514 : // aNew will only ever be paused by calling PauseFromStyle
515 : // making IsPausedOrPausing synonymous in this case.)
516 0 : if (!aOld.IsStylePaused() && aNewIsStylePaused) {
517 0 : aOld.PauseFromStyle();
518 0 : animationChanged = true;
519 0 : } else if (aOld.IsStylePaused() && !aNewIsStylePaused) {
520 0 : aOld.PlayFromStyle();
521 0 : animationChanged = true;
522 : }
523 : }
524 :
525 : // Updating the effect timing above might already have caused the
526 : // animation to become irrelevant so only add a changed record if
527 : // the animation is still relevant.
528 0 : if (animationChanged && aOld.IsRelevant()) {
529 0 : nsNodeUtils::AnimationChanged(&aOld);
530 : }
531 0 : }
532 :
533 : // Returns a new animation set up with given StyleAnimation.
534 : // Or returns an existing animation matching StyleAnimation's name updated
535 : // with the new StyleAnimation.
536 : template<class BuilderType>
537 : static already_AddRefed<CSSAnimation>
538 0 : BuildAnimation(nsPresContext* aPresContext,
539 : const NonOwningAnimationTarget& aTarget,
540 : const StyleAnimation& aSrc,
541 : BuilderType& aBuilder,
542 : nsAnimationManager::CSSAnimationCollection* aCollection)
543 : {
544 0 : MOZ_ASSERT(aPresContext);
545 :
546 0 : nsTArray<Keyframe> keyframes;
547 0 : if (!aBuilder.BuildKeyframes(aPresContext, aSrc, keyframes)) {
548 0 : return nullptr;
549 : }
550 :
551 : TimingParams timing = TimingParamsFromCSSParams(aSrc.GetDuration(),
552 : aSrc.GetDelay(),
553 : aSrc.GetIterationCount(),
554 : aSrc.GetDirection(),
555 0 : aSrc.GetFillMode());
556 :
557 : bool isStylePaused =
558 0 : aSrc.GetPlayState() == NS_STYLE_ANIMATION_PLAY_STATE_PAUSED;
559 :
560 : // Find the matching animation with animation name in the old list
561 : // of animations and remove the matched animation from the list.
562 : RefPtr<CSSAnimation> oldAnim =
563 0 : PopExistingAnimation(aSrc.GetName(), aCollection);
564 :
565 0 : if (oldAnim) {
566 : // Copy over the start times and (if still paused) pause starts
567 : // for each animation (matching on name only) that was also in the
568 : // old list of animations.
569 : // This means that we honor dynamic changes, which isn't what the
570 : // spec says to do, but WebKit seems to honor at least some of
571 : // them. See
572 : // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
573 : // In order to honor what the spec said, we'd copy more data over.
574 0 : UpdateOldAnimationPropertiesWithNew(*oldAnim,
575 : timing,
576 0 : Move(keyframes),
577 : isStylePaused,
578 : aBuilder);
579 0 : return oldAnim.forget();
580 : }
581 :
582 : // mTarget is non-null here, so we emplace it directly.
583 0 : Maybe<OwningAnimationTarget> target;
584 0 : target.emplace(aTarget.mElement, aTarget.mPseudoType);
585 0 : KeyframeEffectParams effectOptions;
586 : RefPtr<KeyframeEffectReadOnly> effect =
587 0 : new KeyframeEffectReadOnly(aPresContext->Document(), target, timing,
588 0 : effectOptions);
589 :
590 0 : aBuilder.SetKeyframes(*effect, Move(keyframes));
591 :
592 : RefPtr<CSSAnimation> animation =
593 0 : new CSSAnimation(aPresContext->Document()->GetScopeObject(),
594 0 : aSrc.GetName());
595 0 : animation->SetOwningElement(
596 0 : OwningElementRef(*aTarget.mElement, aTarget.mPseudoType));
597 :
598 0 : animation->SetTimelineNoUpdate(aTarget.mElement->OwnerDoc()->Timeline());
599 0 : animation->SetEffectNoUpdate(effect);
600 :
601 0 : if (isStylePaused) {
602 0 : animation->PauseFromStyle();
603 : } else {
604 0 : animation->PlayFromStyle();
605 : }
606 :
607 0 : return animation.forget();
608 : }
609 :
610 : bool
611 0 : GeckoCSSAnimationBuilder::BuildKeyframes(nsPresContext* aPresContext,
612 : const StyleAnimation& aSrc,
613 : nsTArray<Keyframe>& aKeyframes)
614 : {
615 0 : MOZ_ASSERT(aPresContext);
616 0 : MOZ_ASSERT(aPresContext->StyleSet()->IsGecko());
617 :
618 : nsCSSKeyframesRule* rule =
619 0 : aPresContext->StyleSet()->AsGecko()->KeyframesRuleForName(aSrc.GetName());
620 0 : if (!rule) {
621 0 : return false;
622 : }
623 :
624 0 : aKeyframes = BuildAnimationFrames(aPresContext, aSrc, rule);
625 :
626 0 : return true;
627 : }
628 :
629 : nsTArray<Keyframe>
630 0 : GeckoCSSAnimationBuilder::BuildAnimationFrames(nsPresContext* aPresContext,
631 : const StyleAnimation& aSrc,
632 : const nsCSSKeyframesRule* aRule)
633 : {
634 : // Ideally we'd like to build up a set of Keyframe objects that more-or-less
635 : // reflect the keyframes as-specified in the @keyframes rule(s) so that
636 : // authors get something intuitive when they call anim.effect.getKeyframes().
637 : //
638 : // That, however, proves to be difficult because the way CSS declarations are
639 : // processed differs from how we are able to represent keyframes as
640 : // JavaScript objects in the Web Animations API.
641 : //
642 : // For example,
643 : //
644 : // { margin: 10px; margin-left: 20px }
645 : //
646 : // could be represented as:
647 : //
648 : // { margin: '10px', marginLeft: '20px' }
649 : //
650 : // BUT:
651 : //
652 : // { margin-left: 20px; margin: 10px }
653 : //
654 : // would be represented as:
655 : //
656 : // { margin: '10px' }
657 : //
658 : // Likewise,
659 : //
660 : // { margin-left: 20px; margin-left: 30px }
661 : //
662 : // would be represented as:
663 : //
664 : // { marginLeft: '30px' }
665 : //
666 : // As such, the mapping between source @keyframes and the Keyframe objects
667 : // becomes obscured. The deviation is even more significant when we consider
668 : // cascading between @keyframes rules and variable references in shorthand
669 : // properties.
670 : //
671 : // We could, perhaps, produce a mapping that makes sense most of the time
672 : // but it would be complex and need to be specified and implemented
673 : // interoperably. Instead, for now, for CSS Animations (and CSS Transitions,
674 : // for that matter) we resolve values on @keyframes down to computed values
675 : // (thereby expanding shorthands and variable references) and then pick up the
676 : // last value for each longhand property at each offset.
677 :
678 : // FIXME: There is a pending spec change to make multiple @keyframes
679 : // rules with the same name cascade but we don't support that yet.
680 :
681 : Maybe<ComputedTimingFunction> inheritedTimingFunction =
682 0 : ConvertTimingFunction(aSrc.GetTimingFunction());
683 :
684 : // First, make up Keyframe objects for each rule
685 0 : nsTArray<Keyframe> keyframes;
686 0 : nsCSSPropertyIDSet animatedProperties;
687 :
688 0 : for (auto ruleIdx = 0, ruleEnd = aRule->StyleRuleCount();
689 0 : ruleIdx != ruleEnd; ++ruleIdx) {
690 0 : css::Rule* cssRule = aRule->GetStyleRuleAt(ruleIdx);
691 0 : MOZ_ASSERT(cssRule, "must have rule");
692 0 : MOZ_ASSERT(cssRule->GetType() == css::Rule::KEYFRAME_RULE,
693 : "must be keyframe rule");
694 0 : nsCSSKeyframeRule* keyframeRule = static_cast<nsCSSKeyframeRule*>(cssRule);
695 :
696 0 : const nsTArray<float>& keys = keyframeRule->GetKeys();
697 0 : for (float key : keys) {
698 0 : if (key < 0.0f || key > 1.0f) {
699 0 : continue;
700 : }
701 :
702 0 : Keyframe keyframe;
703 0 : keyframe.mOffset.emplace(key);
704 : keyframe.mTimingFunction =
705 0 : GetKeyframeTimingFunction(aPresContext, keyframeRule,
706 0 : inheritedTimingFunction);
707 : keyframe.mPropertyValues =
708 0 : GetKeyframePropertyValues(aPresContext, keyframeRule,
709 0 : animatedProperties);
710 :
711 0 : keyframes.AppendElement(Move(keyframe));
712 : }
713 : }
714 :
715 : // Next, stable sort by offset
716 0 : std::stable_sort(keyframes.begin(), keyframes.end(),
717 0 : [](const Keyframe& a, const Keyframe& b)
718 : {
719 0 : return a.mOffset < b.mOffset;
720 0 : });
721 :
722 : // Then walk backwards through the keyframes and drop overridden properties.
723 0 : nsCSSPropertyIDSet propertiesSetAtCurrentOffset;
724 0 : nsCSSPropertyIDSet propertiesSetAtStart;
725 0 : nsCSSPropertyIDSet propertiesSetAtEnd;
726 0 : double currentOffset = -1.0;
727 0 : for (size_t keyframeIdx = keyframes.Length();
728 0 : keyframeIdx > 0;
729 : --keyframeIdx) {
730 0 : Keyframe& keyframe = keyframes[keyframeIdx - 1];
731 0 : MOZ_ASSERT(keyframe.mOffset, "Should have filled in the offset");
732 :
733 0 : if (keyframe.mOffset.value() != currentOffset) {
734 0 : propertiesSetAtCurrentOffset.Empty();
735 0 : currentOffset = keyframe.mOffset.value();
736 : }
737 :
738 : // Get the set of properties from this keyframe that have not
739 : // already been set at this offset.
740 0 : nsTArray<PropertyValuePair> uniquePropertyValues;
741 0 : uniquePropertyValues.SetCapacity(keyframe.mPropertyValues.Length());
742 0 : for (const PropertyValuePair& pair : keyframe.mPropertyValues) {
743 0 : if (!propertiesSetAtCurrentOffset.HasProperty(pair.mProperty)) {
744 0 : uniquePropertyValues.AppendElement(pair);
745 0 : propertiesSetAtCurrentOffset.AddProperty(pair.mProperty);
746 :
747 0 : if (currentOffset == 0.0) {
748 0 : propertiesSetAtStart.AddProperty(pair.mProperty);
749 0 : } else if (currentOffset == 1.0) {
750 0 : propertiesSetAtEnd.AddProperty(pair.mProperty);
751 : }
752 : }
753 : }
754 :
755 : // If we have a keyframe at the same offset with the same timing
756 : // function we should merge our (unique) values into it.
757 : // Otherwise, we should update the existing keyframe with only the
758 : // unique properties.
759 : //
760 : // Bug 1293490: We should also match composite modes here.
761 0 : Keyframe* existingKeyframe = nullptr;
762 : // Don't bother searching for an existing keyframe if we don't
763 : // have anything to contribute to it.
764 0 : if (!uniquePropertyValues.IsEmpty()) {
765 0 : for (size_t i = keyframeIdx; i < keyframes.Length(); i++) {
766 0 : Keyframe& kf = keyframes[i];
767 0 : if (kf.mOffset.value() != currentOffset) {
768 0 : break;
769 : }
770 0 : if (kf.mTimingFunction == keyframe.mTimingFunction) {
771 0 : existingKeyframe = &kf;
772 0 : break;
773 : }
774 : }
775 : }
776 :
777 0 : if (existingKeyframe) {
778 : existingKeyframe->
779 0 : mPropertyValues.AppendElements(Move(uniquePropertyValues));
780 0 : keyframe.mPropertyValues.Clear();
781 : } else {
782 0 : keyframe.mPropertyValues.SwapElements(uniquePropertyValues);
783 : }
784 :
785 : // Check for a now-empty keyframe
786 0 : if (keyframe.mPropertyValues.IsEmpty()) {
787 0 : keyframes.RemoveElementAt(keyframeIdx - 1);
788 : // existingKeyframe might dangle now
789 : }
790 : }
791 :
792 : // Finally, we need to look for any animated properties that have an
793 : // implicit 'to' or 'from' value and fill in the appropriate keyframe
794 : // with the current computed style.
795 : FillInMissingKeyframeValues(animatedProperties, propertiesSetAtStart,
796 : propertiesSetAtEnd, inheritedTimingFunction,
797 0 : keyframes);
798 :
799 0 : return keyframes;
800 : }
801 :
802 : Maybe<ComputedTimingFunction>
803 0 : GeckoCSSAnimationBuilder::GetKeyframeTimingFunction(
804 : nsPresContext* aPresContext,
805 : nsCSSKeyframeRule* aKeyframeRule,
806 : const Maybe<ComputedTimingFunction>& aInheritedTimingFunction)
807 : {
808 0 : Maybe<ComputedTimingFunction> result;
809 :
810 0 : if (aKeyframeRule->Declaration() &&
811 0 : aKeyframeRule->Declaration()->HasProperty(
812 : eCSSProperty_animation_timing_function)) {
813 : RefPtr<nsStyleContext> keyframeRuleContext =
814 : mResolvedStyles.Get(aPresContext, mStyleContext,
815 0 : aKeyframeRule->Declaration());
816 0 : const nsTimingFunction& tf = keyframeRuleContext->StyleDisplay()->
817 0 : mAnimations[0].GetTimingFunction();
818 0 : result = ConvertTimingFunction(tf);
819 : } else {
820 0 : result = aInheritedTimingFunction;
821 : }
822 :
823 0 : return result;
824 : }
825 :
826 : static Maybe<ComputedTimingFunction>
827 0 : ConvertTimingFunction(const nsTimingFunction& aTimingFunction)
828 : {
829 0 : Maybe<ComputedTimingFunction> result;
830 :
831 0 : if (aTimingFunction.mType != nsTimingFunction::Type::Linear) {
832 0 : result.emplace();
833 0 : result->Init(aTimingFunction);
834 : }
835 :
836 0 : return result;
837 : }
838 :
839 : nsTArray<PropertyValuePair>
840 0 : GeckoCSSAnimationBuilder::GetKeyframePropertyValues(
841 : nsPresContext* aPresContext,
842 : nsCSSKeyframeRule* aKeyframeRule,
843 : nsCSSPropertyIDSet& aAnimatedProperties)
844 : {
845 0 : nsTArray<PropertyValuePair> result;
846 : RefPtr<nsStyleContext> styleContext =
847 : mResolvedStyles.Get(aPresContext, mStyleContext,
848 0 : aKeyframeRule->Declaration());
849 :
850 0 : for (nsCSSPropertyID prop = nsCSSPropertyID(0);
851 0 : prop < eCSSProperty_COUNT_no_shorthands;
852 0 : prop = nsCSSPropertyID(prop + 1)) {
853 0 : if (nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None ||
854 0 : !aKeyframeRule->Declaration()->HasNonImportantValueFor(prop)) {
855 0 : continue;
856 : }
857 :
858 0 : StyleAnimationValue computedValue;
859 0 : if (!StyleAnimationValue::ExtractComputedValue(prop, styleContext,
860 : computedValue)) {
861 0 : continue;
862 : }
863 :
864 0 : nsCSSValue propertyValue;
865 : DebugOnly<bool> uncomputeResult =
866 0 : StyleAnimationValue::UncomputeValue(prop, Move(computedValue),
867 0 : propertyValue);
868 0 : MOZ_ASSERT(uncomputeResult,
869 : "Unable to get specified value from computed value");
870 0 : MOZ_ASSERT(propertyValue.GetUnit() != eCSSUnit_Null,
871 : "Not expecting to read invalid properties");
872 :
873 0 : result.AppendElement(Move(PropertyValuePair(prop, Move(propertyValue))));
874 0 : aAnimatedProperties.AddProperty(prop);
875 : }
876 :
877 0 : return result;
878 : }
879 :
880 : void
881 0 : GeckoCSSAnimationBuilder::FillInMissingKeyframeValues(
882 : nsCSSPropertyIDSet aAnimatedProperties,
883 : nsCSSPropertyIDSet aPropertiesSetAtStart,
884 : nsCSSPropertyIDSet aPropertiesSetAtEnd,
885 : const Maybe<ComputedTimingFunction>& aInheritedTimingFunction,
886 : nsTArray<Keyframe>& aKeyframes)
887 : {
888 : static const size_t kNotSet = static_cast<size_t>(-1);
889 :
890 : // Find/create the keyframe to add start values to
891 0 : size_t startKeyframeIndex = kNotSet;
892 0 : if (!aAnimatedProperties.Equals(aPropertiesSetAtStart) &&
893 0 : !nsAnimationManager::FindMatchingKeyframe(aKeyframes,
894 : 0.0,
895 : aInheritedTimingFunction,
896 : startKeyframeIndex)) {
897 0 : Keyframe newKeyframe;
898 0 : newKeyframe.mOffset.emplace(0.0);
899 0 : newKeyframe.mTimingFunction = aInheritedTimingFunction;
900 0 : aKeyframes.InsertElementAt(startKeyframeIndex, Move(newKeyframe));
901 : }
902 :
903 : // Find/create the keyframe to add end values to
904 0 : size_t endKeyframeIndex = kNotSet;
905 0 : if (!aAnimatedProperties.Equals(aPropertiesSetAtEnd)) {
906 0 : if (!nsAnimationManager::FindMatchingKeyframe(Reversed(aKeyframes),
907 : 1.0,
908 : aInheritedTimingFunction,
909 : endKeyframeIndex)) {
910 0 : Keyframe newKeyframe;
911 0 : newKeyframe.mOffset.emplace(1.0);
912 0 : newKeyframe.mTimingFunction = aInheritedTimingFunction;
913 0 : aKeyframes.AppendElement(Move(newKeyframe));
914 0 : endKeyframeIndex = aKeyframes.Length() - 1;
915 : } else {
916 : // endKeyframeIndex is currently a count from the end of the array
917 : // so we need to reverse it.
918 0 : endKeyframeIndex = aKeyframes.Length() - 1 - endKeyframeIndex;
919 : }
920 : }
921 :
922 0 : if (startKeyframeIndex == kNotSet && endKeyframeIndex == kNotSet) {
923 0 : return;
924 : }
925 :
926 : // Now that we have finished manipulating aKeyframes, it is safe to
927 : // take pointers to its elements.
928 0 : Keyframe* startKeyframe = startKeyframeIndex == kNotSet
929 0 : ? nullptr : &aKeyframes[startKeyframeIndex];
930 0 : Keyframe* endKeyframe = endKeyframeIndex == kNotSet
931 0 : ? nullptr : &aKeyframes[endKeyframeIndex];
932 :
933 : // Iterate through all properties and fill-in missing values
934 0 : for (nsCSSPropertyID prop = nsCSSPropertyID(0);
935 0 : prop < eCSSProperty_COUNT_no_shorthands;
936 0 : prop = nsCSSPropertyID(prop + 1)) {
937 0 : if (!aAnimatedProperties.HasProperty(prop)) {
938 0 : continue;
939 : }
940 :
941 0 : if (startKeyframe && !aPropertiesSetAtStart.HasProperty(prop)) {
942 : // An uninitialized nsCSSValue represents the underlying value.
943 0 : PropertyValuePair propertyValue(prop, Move(nsCSSValue()));
944 0 : startKeyframe->mPropertyValues.AppendElement(Move(propertyValue));
945 : }
946 0 : if (endKeyframe && !aPropertiesSetAtEnd.HasProperty(prop)) {
947 : // An uninitialized nsCSSValue represents the underlying value.
948 0 : PropertyValuePair propertyValue(prop, Move(nsCSSValue()));
949 0 : endKeyframe->mPropertyValues.AppendElement(Move(propertyValue));
950 : }
951 : }
952 : }
953 :
954 : template<class BuilderType>
955 : static nsAnimationManager::OwningCSSAnimationPtrArray
956 0 : BuildAnimations(nsPresContext* aPresContext,
957 : const NonOwningAnimationTarget& aTarget,
958 : const nsStyleAutoArray<StyleAnimation>& aStyleAnimations,
959 : uint32_t aStyleAnimationNameCount,
960 : BuilderType& aBuilder,
961 : nsAnimationManager::CSSAnimationCollection* aCollection)
962 : {
963 0 : nsAnimationManager::OwningCSSAnimationPtrArray result;
964 :
965 0 : for (size_t animIdx = aStyleAnimationNameCount; animIdx-- != 0;) {
966 0 : const StyleAnimation& src = aStyleAnimations[animIdx];
967 :
968 : // CSS Animations whose animation-name does not match a @keyframes rule do
969 : // not generate animation events. This includes when the animation-name is
970 : // "none" which is represented by an empty name in the StyleAnimation.
971 : // Since such animations neither affect style nor dispatch events, we do
972 : // not generate a corresponding CSSAnimation for them.
973 0 : if (src.GetName().IsEmpty()) {
974 0 : continue;
975 : }
976 :
977 : RefPtr<CSSAnimation> dest = BuildAnimation(aPresContext,
978 : aTarget,
979 : src,
980 : aBuilder,
981 0 : aCollection);
982 0 : if (!dest) {
983 0 : continue;
984 : }
985 :
986 0 : dest->SetAnimationIndex(static_cast<uint64_t>(animIdx));
987 0 : result.AppendElement(dest);
988 : }
989 0 : return result;
990 : }
991 :
992 : void
993 2834 : nsAnimationManager::UpdateAnimations(nsStyleContext* aStyleContext,
994 : mozilla::dom::Element* aElement)
995 : {
996 2834 : MOZ_ASSERT(mPresContext->IsDynamic(),
997 : "Should not update animations for print or print preview");
998 2834 : MOZ_ASSERT(aElement->IsInComposedDoc(),
999 : "Should not update animations that are not attached to the "
1000 : "document tree");
1001 :
1002 2834 : if (aStyleContext->IsInDisplayNoneSubtree()) {
1003 1205 : StopAnimationsForElement(aElement, aStyleContext->GetPseudoType());
1004 1205 : return;
1005 : }
1006 :
1007 1629 : NonOwningAnimationTarget target(aElement, aStyleContext->GetPseudoType());
1008 3258 : GeckoCSSAnimationBuilder builder(aStyleContext, target);
1009 :
1010 1629 : const nsStyleDisplay* disp = aStyleContext->StyleDisplay();
1011 1629 : DoUpdateAnimations(target, *disp, builder);
1012 : }
1013 :
1014 : void
1015 0 : nsAnimationManager::UpdateAnimations(
1016 : dom::Element* aElement,
1017 : CSSPseudoElementType aPseudoType,
1018 : const ServoComputedValues* aComputedValues)
1019 : {
1020 0 : MOZ_ASSERT(mPresContext->IsDynamic(),
1021 : "Should not update animations for print or print preview");
1022 0 : MOZ_ASSERT(aElement->IsInComposedDoc(),
1023 : "Should not update animations that are not attached to the "
1024 : "document tree");
1025 :
1026 0 : if (!aComputedValues) {
1027 : // If we are in a display:none subtree we will have no computed values.
1028 : // Since CSS animations should not run in display:none subtrees we should
1029 : // stop (actually, destroy) any animations on this element here.
1030 0 : StopAnimationsForElement(aElement, aPseudoType);
1031 0 : return;
1032 : }
1033 :
1034 0 : NonOwningAnimationTarget target(aElement, aPseudoType);
1035 0 : ServoCSSAnimationBuilder builder(aComputedValues);
1036 :
1037 : const nsStyleDisplay *disp =
1038 0 : Servo_GetStyleDisplay(aComputedValues);
1039 0 : DoUpdateAnimations(target, *disp, builder);
1040 : }
1041 :
1042 : template<class BuilderType>
1043 : void
1044 1629 : nsAnimationManager::DoUpdateAnimations(
1045 : const NonOwningAnimationTarget& aTarget,
1046 : const nsStyleDisplay& aStyleDisplay,
1047 : BuilderType& aBuilder)
1048 : {
1049 : // Everything that causes our animation data to change triggers a
1050 : // style change, which in turn triggers a non-animation restyle.
1051 : // Likewise, when we initially construct frames, we're not in a
1052 : // style change, but also not in an animation restyle.
1053 :
1054 : CSSAnimationCollection* collection =
1055 1629 : CSSAnimationCollection::GetAnimationCollection(aTarget.mElement,
1056 3258 : aTarget.mPseudoType);
1057 3258 : if (!collection &&
1058 3258 : aStyleDisplay.mAnimationNameCount == 1 &&
1059 1629 : aStyleDisplay.mAnimations[0].GetName().IsEmpty()) {
1060 3258 : return;
1061 : }
1062 :
1063 0 : nsAutoAnimationMutationBatch mb(aTarget.mElement->OwnerDoc());
1064 :
1065 : // Build the updated animations list, extracting matching animations from
1066 : // the existing collection as we go.
1067 0 : OwningCSSAnimationPtrArray newAnimations;
1068 0 : newAnimations = BuildAnimations(mPresContext,
1069 : aTarget,
1070 : aStyleDisplay.mAnimations,
1071 0 : aStyleDisplay.mAnimationNameCount,
1072 : aBuilder,
1073 : collection);
1074 :
1075 0 : if (newAnimations.IsEmpty()) {
1076 0 : if (collection) {
1077 0 : collection->Destroy();
1078 : }
1079 0 : return;
1080 : }
1081 :
1082 0 : if (!collection) {
1083 0 : bool createdCollection = false;
1084 0 : collection =
1085 : CSSAnimationCollection::GetOrCreateAnimationCollection(
1086 0 : aTarget.mElement, aTarget.mPseudoType, &createdCollection);
1087 0 : if (!collection) {
1088 0 : MOZ_ASSERT(!createdCollection, "outparam should agree with return value");
1089 0 : NS_WARNING("allocating collection failed");
1090 0 : return;
1091 : }
1092 :
1093 0 : if (createdCollection) {
1094 0 : AddElementCollection(collection);
1095 : }
1096 : }
1097 0 : collection->mAnimations.SwapElements(newAnimations);
1098 :
1099 : // Cancel removed animations
1100 0 : for (size_t newAnimIdx = newAnimations.Length(); newAnimIdx-- != 0; ) {
1101 0 : newAnimations[newAnimIdx]->CancelFromStyle();
1102 : }
1103 : }
|