nsAnimationManager.cpp 39 KB

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