nsTransitionManager.cpp 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  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. /* Code to start and animate CSS transitions. */
  6. #include "nsTransitionManager.h"
  7. #include "nsAnimationManager.h"
  8. #include "mozilla/dom/CSSTransitionBinding.h"
  9. #include "nsIContent.h"
  10. #include "nsStyleContext.h"
  11. #include "mozilla/MemoryReporting.h"
  12. #include "mozilla/TimeStamp.h"
  13. #include "nsRefreshDriver.h"
  14. #include "nsRuleProcessorData.h"
  15. #include "nsRuleWalker.h"
  16. #include "nsCSSPropertyIDSet.h"
  17. #include "mozilla/EffectCompositor.h"
  18. #include "mozilla/EffectSet.h"
  19. #include "mozilla/EventDispatcher.h"
  20. #include "mozilla/StyleAnimationValue.h"
  21. #include "mozilla/dom/DocumentTimeline.h"
  22. #include "mozilla/dom/Element.h"
  23. #include "nsIFrame.h"
  24. #include "Layers.h"
  25. #include "FrameLayerBuilder.h"
  26. #include "nsCSSProps.h"
  27. #include "nsCSSPseudoElements.h"
  28. #include "nsDisplayList.h"
  29. #include "nsStyleChangeList.h"
  30. #include "nsStyleSet.h"
  31. #include "mozilla/RestyleManagerHandle.h"
  32. #include "mozilla/RestyleManagerHandleInlines.h"
  33. #include "nsDOMMutationObserver.h"
  34. using mozilla::TimeStamp;
  35. using mozilla::TimeDuration;
  36. using mozilla::dom::Animation;
  37. using mozilla::dom::AnimationPlayState;
  38. using mozilla::dom::CSSTransition;
  39. using mozilla::dom::KeyframeEffectReadOnly;
  40. using namespace mozilla;
  41. using namespace mozilla::css;
  42. namespace {
  43. struct TransitionEventParams {
  44. EventMessage mMessage;
  45. StickyTimeDuration mElapsedTime;
  46. TimeStamp mTimeStamp;
  47. };
  48. } // anonymous namespace
  49. double
  50. ElementPropertyTransition::CurrentValuePortion() const
  51. {
  52. MOZ_ASSERT(!GetLocalTime().IsNull(),
  53. "Getting the value portion of an animation that's not being "
  54. "sampled");
  55. // Transitions use a fill mode of 'backwards' so GetComputedTiming will
  56. // never return a null time progress due to being *before* the animation
  57. // interval. However, it might be possible that we're behind on flushing
  58. // causing us to get called *after* the animation interval. So, just in
  59. // case, we override the fill mode to 'both' to ensure the progress
  60. // is never null.
  61. TimingParams timingToUse = SpecifiedTiming();
  62. timingToUse.mFill = dom::FillMode::Both;
  63. ComputedTiming computedTiming = GetComputedTiming(&timingToUse);
  64. MOZ_ASSERT(!computedTiming.mProgress.IsNull(),
  65. "Got a null progress for a fill mode of 'both'");
  66. MOZ_ASSERT(mKeyframes.Length() == 2,
  67. "Should have two animation keyframes for a transition");
  68. return ComputedTimingFunction::GetPortion(mKeyframes[0].mTimingFunction,
  69. computedTiming.mProgress.Value(),
  70. computedTiming.mBeforeFlag);
  71. }
  72. void
  73. ElementPropertyTransition::UpdateStartValueFromReplacedTransition()
  74. {
  75. if (!mReplacedTransition) {
  76. return;
  77. }
  78. MOZ_ASSERT(nsCSSProps::PropHasFlags(TransitionProperty(),
  79. CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
  80. "The transition property should be able to be run on the "
  81. "compositor");
  82. MOZ_ASSERT(mTarget && mTarget->mElement->OwnerDoc(),
  83. "We should have a valid document at this moment");
  84. dom::DocumentTimeline* timeline = mTarget->mElement->OwnerDoc()->Timeline();
  85. ComputedTiming computedTiming = GetComputedTimingAt(
  86. dom::CSSTransition::GetCurrentTimeAt(*timeline,
  87. TimeStamp::Now(),
  88. mReplacedTransition->mStartTime,
  89. mReplacedTransition->mPlaybackRate),
  90. mReplacedTransition->mTiming,
  91. mReplacedTransition->mPlaybackRate);
  92. if (!computedTiming.mProgress.IsNull()) {
  93. double valuePosition =
  94. ComputedTimingFunction::GetPortion(mReplacedTransition->mTimingFunction,
  95. computedTiming.mProgress.Value(),
  96. computedTiming.mBeforeFlag);
  97. StyleAnimationValue startValue;
  98. if (StyleAnimationValue::Interpolate(mProperties[0].mProperty,
  99. mReplacedTransition->mFromValue,
  100. mReplacedTransition->mToValue,
  101. valuePosition, startValue)) {
  102. MOZ_ASSERT(mProperties.Length() == 1 &&
  103. mProperties[0].mSegments.Length() == 1,
  104. "The transition should have one property and one segment");
  105. nsCSSValue cssValue;
  106. DebugOnly<bool> uncomputeResult =
  107. StyleAnimationValue::UncomputeValue(mProperties[0].mProperty,
  108. startValue,
  109. cssValue);
  110. mProperties[0].mSegments[0].mFromValue = Move(startValue);
  111. MOZ_ASSERT(uncomputeResult, "UncomputeValue should not fail");
  112. MOZ_ASSERT(mKeyframes.Length() == 2,
  113. "Transitions should have exactly two animation keyframes");
  114. MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1,
  115. "Transitions should have exactly one property in their first "
  116. "frame");
  117. mKeyframes[0].mPropertyValues[0].mValue = cssValue;
  118. }
  119. }
  120. mReplacedTransition.reset();
  121. }
  122. ////////////////////////// CSSTransition ////////////////////////////
  123. JSObject*
  124. CSSTransition::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
  125. {
  126. return dom::CSSTransitionBinding::Wrap(aCx, this, aGivenProto);
  127. }
  128. void
  129. CSSTransition::GetTransitionProperty(nsString& aRetVal) const
  130. {
  131. MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
  132. "Transition Property should be initialized");
  133. aRetVal =
  134. NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mTransitionProperty));
  135. }
  136. AnimationPlayState
  137. CSSTransition::PlayStateFromJS() const
  138. {
  139. FlushStyle();
  140. return Animation::PlayStateFromJS();
  141. }
  142. void
  143. CSSTransition::PlayFromJS(ErrorResult& aRv)
  144. {
  145. FlushStyle();
  146. Animation::PlayFromJS(aRv);
  147. }
  148. void
  149. CSSTransition::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
  150. {
  151. if (mNeedsNewAnimationIndexWhenRun &&
  152. PlayState() != AnimationPlayState::Idle) {
  153. mAnimationIndex = sNextAnimationIndex++;
  154. mNeedsNewAnimationIndexWhenRun = false;
  155. }
  156. Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
  157. }
  158. void
  159. CSSTransition::QueueEvents(StickyTimeDuration aActiveTime)
  160. {
  161. if (!mOwningElement.IsSet()) {
  162. return;
  163. }
  164. dom::Element* owningElement;
  165. CSSPseudoElementType owningPseudoType;
  166. mOwningElement.GetElement(owningElement, owningPseudoType);
  167. MOZ_ASSERT(owningElement, "Owning element should be set");
  168. nsPresContext* presContext = mOwningElement.GetRenderedPresContext();
  169. if (!presContext) {
  170. return;
  171. }
  172. const StickyTimeDuration zeroDuration = StickyTimeDuration();
  173. TransitionPhase currentPhase;
  174. StickyTimeDuration intervalStartTime;
  175. StickyTimeDuration intervalEndTime;
  176. if (!mEffect) {
  177. currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
  178. intervalStartTime = zeroDuration;
  179. intervalEndTime = zeroDuration;
  180. } else {
  181. ComputedTiming computedTiming = mEffect->GetComputedTiming();
  182. currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
  183. intervalStartTime =
  184. std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
  185. computedTiming.mActiveDuration), zeroDuration);
  186. intervalEndTime =
  187. std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
  188. computedTiming.mActiveDuration), zeroDuration);
  189. }
  190. // TimeStamps to use for ordering the events when they are dispatched. We
  191. // use a TimeStamp so we can compare events produced by different elements,
  192. // perhaps even with different timelines.
  193. // The zero timestamp is for transitionrun events where we ignore the delay
  194. // for the purpose of ordering events.
  195. TimeStamp zeroTimeStamp = AnimationTimeToTimeStamp(zeroDuration);
  196. TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
  197. TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
  198. if (mPendingState != PendingState::NotPending &&
  199. (mPreviousTransitionPhase == TransitionPhase::Idle ||
  200. mPreviousTransitionPhase == TransitionPhase::Pending))
  201. {
  202. currentPhase = TransitionPhase::Pending;
  203. }
  204. AutoTArray<TransitionEventParams, 3> events;
  205. // Handle cancel events firts
  206. if (mPreviousTransitionPhase != TransitionPhase::Idle &&
  207. currentPhase == TransitionPhase::Idle) {
  208. TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
  209. events.AppendElement(TransitionEventParams{ eTransitionCancel,
  210. aActiveTime,
  211. activeTimeStamp });
  212. }
  213. // All other events
  214. switch (mPreviousTransitionPhase) {
  215. case TransitionPhase::Idle:
  216. if (currentPhase == TransitionPhase::Pending ||
  217. currentPhase == TransitionPhase::Before) {
  218. events.AppendElement(TransitionEventParams{ eTransitionRun,
  219. intervalStartTime,
  220. zeroTimeStamp });
  221. } else if (currentPhase == TransitionPhase::Active) {
  222. events.AppendElement(TransitionEventParams{ eTransitionRun,
  223. intervalStartTime,
  224. zeroTimeStamp });
  225. events.AppendElement(TransitionEventParams{ eTransitionStart,
  226. intervalStartTime,
  227. startTimeStamp });
  228. } else if (currentPhase == TransitionPhase::After) {
  229. events.AppendElement(TransitionEventParams{ eTransitionRun,
  230. intervalStartTime,
  231. zeroTimeStamp });
  232. events.AppendElement(TransitionEventParams{ eTransitionStart,
  233. intervalStartTime,
  234. startTimeStamp });
  235. events.AppendElement(TransitionEventParams{ eTransitionEnd,
  236. intervalEndTime,
  237. endTimeStamp });
  238. }
  239. break;
  240. case TransitionPhase::Pending:
  241. case TransitionPhase::Before:
  242. if (currentPhase == TransitionPhase::Active) {
  243. events.AppendElement(TransitionEventParams{ eTransitionStart,
  244. intervalStartTime,
  245. startTimeStamp });
  246. } else if (currentPhase == TransitionPhase::After) {
  247. events.AppendElement(TransitionEventParams{ eTransitionStart,
  248. intervalStartTime,
  249. startTimeStamp });
  250. events.AppendElement(TransitionEventParams{ eTransitionEnd,
  251. intervalEndTime,
  252. endTimeStamp });
  253. }
  254. break;
  255. case TransitionPhase::Active:
  256. if (currentPhase == TransitionPhase::After) {
  257. events.AppendElement(TransitionEventParams{ eTransitionEnd,
  258. intervalEndTime,
  259. endTimeStamp });
  260. } else if (currentPhase == TransitionPhase::Before) {
  261. events.AppendElement(TransitionEventParams{ eTransitionEnd,
  262. intervalStartTime,
  263. startTimeStamp });
  264. }
  265. break;
  266. case TransitionPhase::After:
  267. if (currentPhase == TransitionPhase::Active) {
  268. events.AppendElement(TransitionEventParams{ eTransitionStart,
  269. intervalEndTime,
  270. startTimeStamp });
  271. } else if (currentPhase == TransitionPhase::Before) {
  272. events.AppendElement(TransitionEventParams{ eTransitionStart,
  273. intervalEndTime,
  274. startTimeStamp });
  275. events.AppendElement(TransitionEventParams{ eTransitionEnd,
  276. intervalStartTime,
  277. endTimeStamp });
  278. }
  279. break;
  280. }
  281. mPreviousTransitionPhase = currentPhase;
  282. nsTransitionManager* manager = presContext->TransitionManager();
  283. for (const TransitionEventParams& evt : events) {
  284. manager->QueueEvent(TransitionEventInfo(owningElement, owningPseudoType,
  285. evt.mMessage,
  286. TransitionProperty(),
  287. evt.mElapsedTime,
  288. evt.mTimeStamp,
  289. this));
  290. }
  291. }
  292. void
  293. CSSTransition::Tick()
  294. {
  295. Animation::Tick();
  296. QueueEvents();
  297. }
  298. nsCSSPropertyID
  299. CSSTransition::TransitionProperty() const
  300. {
  301. MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
  302. "Transition property should be initialized");
  303. return mTransitionProperty;
  304. }
  305. StyleAnimationValue
  306. CSSTransition::ToValue() const
  307. {
  308. MOZ_ASSERT(!mTransitionToValue.IsNull(),
  309. "Transition ToValue should be initialized");
  310. return mTransitionToValue;
  311. }
  312. bool
  313. CSSTransition::HasLowerCompositeOrderThan(const CSSTransition& aOther) const
  314. {
  315. MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
  316. "Should only be called for CSS transitions that are sorted "
  317. "as CSS transitions (i.e. tied to CSS markup)");
  318. // 0. Object-equality case
  319. if (&aOther == this) {
  320. return false;
  321. }
  322. // 1. Sort by document order
  323. if (!mOwningElement.Equals(aOther.mOwningElement)) {
  324. return mOwningElement.LessThan(aOther.mOwningElement);
  325. }
  326. // 2. (Same element and pseudo): Sort by transition generation
  327. if (mAnimationIndex != aOther.mAnimationIndex) {
  328. return mAnimationIndex < aOther.mAnimationIndex;
  329. }
  330. // 3. (Same transition generation): Sort by transition property
  331. return nsCSSProps::GetStringValue(TransitionProperty()) <
  332. nsCSSProps::GetStringValue(aOther.TransitionProperty());
  333. }
  334. /* static */ Nullable<TimeDuration>
  335. CSSTransition::GetCurrentTimeAt(const dom::DocumentTimeline& aTimeline,
  336. const TimeStamp& aBaseTime,
  337. const TimeDuration& aStartTime,
  338. double aPlaybackRate)
  339. {
  340. Nullable<TimeDuration> result;
  341. Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime);
  342. if (!timelineTime.IsNull()) {
  343. result.SetValue((timelineTime.Value() - aStartTime)
  344. .MultDouble(aPlaybackRate));
  345. }
  346. return result;
  347. }
  348. void
  349. CSSTransition::SetEffectFromStyle(dom::AnimationEffectReadOnly* aEffect)
  350. {
  351. Animation::SetEffectNoUpdate(aEffect);
  352. // Initialize transition property.
  353. ElementPropertyTransition* pt = aEffect ? aEffect->AsTransition() : nullptr;
  354. if (eCSSProperty_UNKNOWN == mTransitionProperty && pt) {
  355. mTransitionProperty = pt->TransitionProperty();
  356. mTransitionToValue = pt->ToValue();
  357. }
  358. }
  359. ////////////////////////// nsTransitionManager ////////////////////////////
  360. NS_IMPL_CYCLE_COLLECTION(nsTransitionManager, mEventDispatcher)
  361. NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTransitionManager, AddRef)
  362. NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTransitionManager, Release)
  363. static inline bool
  364. ExtractNonDiscreteComputedValue(nsCSSPropertyID aProperty,
  365. nsStyleContext* aStyleContext,
  366. StyleAnimationValue& aComputedValue)
  367. {
  368. return (nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_Discrete ||
  369. aProperty == eCSSProperty_visibility) &&
  370. StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext,
  371. aComputedValue);
  372. }
  373. void
  374. nsTransitionManager::StyleContextChanged(dom::Element *aElement,
  375. nsStyleContext *aOldStyleContext,
  376. RefPtr<nsStyleContext>* aNewStyleContext /* inout */)
  377. {
  378. nsStyleContext* newStyleContext = *aNewStyleContext;
  379. NS_PRECONDITION(aOldStyleContext->GetPseudo() == newStyleContext->GetPseudo(),
  380. "pseudo type mismatch");
  381. if (mInAnimationOnlyStyleUpdate) {
  382. // If we're doing an animation-only style update, return, since the
  383. // purpose of an animation-only style update is to update only the
  384. // animation styles so that we don't consider style changes
  385. // resulting from changes in the animation time for starting a
  386. // transition.
  387. return;
  388. }
  389. if (!mPresContext->IsDynamic()) {
  390. // For print or print preview, ignore transitions.
  391. return;
  392. }
  393. if (aOldStyleContext->HasPseudoElementData() !=
  394. newStyleContext->HasPseudoElementData()) {
  395. // If the old style context and new style context differ in terms of
  396. // whether they're inside ::first-letter, ::first-line, or similar,
  397. // bail. We can't hit this codepath for normal style changes
  398. // involving moving frames around the boundaries of these
  399. // pseudo-elements since we don't call StyleContextChanged from
  400. // ReparentStyleContext. However, we can hit this codepath during
  401. // the handling of transitions that start across reframes.
  402. //
  403. // While there isn't an easy *perfect* way to handle this case, err
  404. // on the side of missing some transitions that we ought to have
  405. // rather than having bogus transitions that we shouldn't.
  406. //
  407. // We could consider changing this handling, although it's worth
  408. // thinking about whether the code below could do anything weird in
  409. // this case.
  410. return;
  411. }
  412. // NOTE: Things in this function (and ConsiderInitiatingTransition)
  413. // should never call PeekStyleData because we don't preserve gotten
  414. // structs across reframes.
  415. // Return sooner (before the startedAny check below) for the most
  416. // common case: no transitions specified or running.
  417. const nsStyleDisplay *disp = newStyleContext->StyleDisplay();
  418. CSSPseudoElementType pseudoType = newStyleContext->GetPseudoType();
  419. if (pseudoType != CSSPseudoElementType::NotPseudo) {
  420. if (pseudoType != CSSPseudoElementType::before &&
  421. pseudoType != CSSPseudoElementType::after) {
  422. return;
  423. }
  424. NS_ASSERTION((pseudoType == CSSPseudoElementType::before &&
  425. aElement->IsGeneratedContentContainerForBefore()) ||
  426. (pseudoType == CSSPseudoElementType::after &&
  427. aElement->IsGeneratedContentContainerForAfter()),
  428. "Unexpected aElement coming through");
  429. // Else the element we want to use from now on is the element the
  430. // :before or :after is attached to.
  431. aElement = aElement->GetParent()->AsElement();
  432. }
  433. CSSTransitionCollection* collection =
  434. CSSTransitionCollection::GetAnimationCollection(aElement, pseudoType);
  435. if (!collection &&
  436. disp->mTransitionPropertyCount == 1 &&
  437. disp->mTransitions[0].GetCombinedDuration() <= 0.0f) {
  438. return;
  439. }
  440. MOZ_ASSERT(mPresContext->RestyleManager()->IsGecko(),
  441. "ServoRestyleManager should not use nsTransitionManager "
  442. "for transitions");
  443. if (collection &&
  444. collection->mCheckGeneration ==
  445. mPresContext->RestyleManager()->AsGecko()->GetAnimationGeneration()) {
  446. // When we start a new transition, we immediately post a restyle.
  447. // If the animation generation on the collection is current, that
  448. // means *this* is that restyle, since we bump the animation
  449. // generation on the restyle manager whenever there's a real style
  450. // change (i.e., one where mInAnimationOnlyStyleUpdate isn't true,
  451. // which causes us to return above). Thus we shouldn't do anything.
  452. return;
  453. }
  454. if (newStyleContext->GetParent() &&
  455. newStyleContext->GetParent()->HasPseudoElementData()) {
  456. // Ignore transitions on things that inherit properties from
  457. // pseudo-elements.
  458. // FIXME (Bug 522599): Add tests for this.
  459. return;
  460. }
  461. NS_WARNING_ASSERTION(
  462. !mPresContext->EffectCompositor()->HasThrottledStyleUpdates(),
  463. "throttled animations not up to date");
  464. // Compute what the css-transitions spec calls the "after-change
  465. // style", which is the new style without any data from transitions,
  466. // but still inheriting from data that contains transitions that are
  467. // not stopping or starting right now.
  468. RefPtr<nsStyleContext> afterChangeStyle;
  469. if (collection) {
  470. MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
  471. "ServoStyleSets should not use nsTransitionManager "
  472. "for transitions");
  473. nsStyleSet* styleSet = mPresContext->StyleSet()->AsGecko();
  474. afterChangeStyle =
  475. styleSet->ResolveStyleWithoutAnimation(aElement, newStyleContext,
  476. eRestyle_CSSTransitions);
  477. } else {
  478. afterChangeStyle = newStyleContext;
  479. }
  480. nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
  481. DebugOnly<bool> startedAny = false;
  482. // We don't have to update transitions if display:none, although we will
  483. // cancel them after restyling.
  484. if (!afterChangeStyle->IsInDisplayNoneSubtree()) {
  485. startedAny = UpdateTransitions(disp, aElement, collection,
  486. aOldStyleContext, afterChangeStyle);
  487. }
  488. MOZ_ASSERT(!startedAny || collection,
  489. "must have element transitions if we started any transitions");
  490. EffectCompositor::CascadeLevel cascadeLevel =
  491. EffectCompositor::CascadeLevel::Transitions;
  492. if (collection) {
  493. collection->UpdateCheckGeneration(mPresContext);
  494. mPresContext->EffectCompositor()->MaybeUpdateAnimationRule(aElement,
  495. pseudoType,
  496. cascadeLevel,
  497. newStyleContext);
  498. }
  499. // We want to replace the new style context with the after-change style.
  500. *aNewStyleContext = afterChangeStyle;
  501. if (collection) {
  502. // Since we have transition styles, we have to undo this replacement.
  503. // The check of collection->mCheckGeneration against the restyle
  504. // manager's GetAnimationGeneration() will ensure that we don't go
  505. // through the rest of this function again when we do.
  506. mPresContext->EffectCompositor()->PostRestyleForAnimation(aElement,
  507. pseudoType,
  508. cascadeLevel);
  509. }
  510. }
  511. bool
  512. nsTransitionManager::UpdateTransitions(
  513. const nsStyleDisplay* aDisp,
  514. dom::Element* aElement,
  515. CSSTransitionCollection*& aElementTransitions,
  516. nsStyleContext* aOldStyleContext,
  517. nsStyleContext* aNewStyleContext)
  518. {
  519. MOZ_ASSERT(aDisp, "Null nsStyleDisplay");
  520. MOZ_ASSERT(!aElementTransitions ||
  521. aElementTransitions->mElement == aElement, "Element mismatch");
  522. // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
  523. // I'll consider only the transitions from the number of items in
  524. // 'transition-property' on down, and later ones will override earlier
  525. // ones (tracked using |whichStarted|).
  526. bool startedAny = false;
  527. nsCSSPropertyIDSet whichStarted;
  528. for (uint32_t i = aDisp->mTransitionPropertyCount; i-- != 0; ) {
  529. const StyleTransition& t = aDisp->mTransitions[i];
  530. // Check the combined duration (combination of delay and duration)
  531. // first, since it defaults to zero, which means we can ignore the
  532. // transition.
  533. if (t.GetCombinedDuration() > 0.0f) {
  534. // We might have something to transition. See if any of the
  535. // properties in question changed and are animatable.
  536. // FIXME: Would be good to find a way to share code between this
  537. // interpretation of transition-property and the one below.
  538. nsCSSPropertyID property = t.GetProperty();
  539. if (property == eCSSPropertyExtra_no_properties ||
  540. property == eCSSPropertyExtra_variable ||
  541. property == eCSSProperty_UNKNOWN) {
  542. // Nothing to do, but need to exclude this from cases below.
  543. } else if (property == eCSSPropertyExtra_all_properties) {
  544. for (nsCSSPropertyID p = nsCSSPropertyID(0);
  545. p < eCSSProperty_COUNT_no_shorthands;
  546. p = nsCSSPropertyID(p + 1)) {
  547. ConsiderInitiatingTransition(p, t, aElement, aElementTransitions,
  548. aOldStyleContext, aNewStyleContext,
  549. &startedAny, &whichStarted);
  550. }
  551. } else if (nsCSSProps::IsShorthand(property)) {
  552. CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property,
  553. CSSEnabledState::eForAllContent)
  554. {
  555. ConsiderInitiatingTransition(*subprop, t, aElement,
  556. aElementTransitions,
  557. aOldStyleContext, aNewStyleContext,
  558. &startedAny, &whichStarted);
  559. }
  560. } else {
  561. ConsiderInitiatingTransition(property, t, aElement, aElementTransitions,
  562. aOldStyleContext, aNewStyleContext,
  563. &startedAny, &whichStarted);
  564. }
  565. }
  566. }
  567. // Stop any transitions for properties that are no longer in
  568. // 'transition-property', including finished transitions.
  569. // Also stop any transitions (and remove any finished transitions)
  570. // for properties that just changed (and are still in the set of
  571. // properties to transition), but for which we didn't just start the
  572. // transition. This can happen delay and duration are both zero, or
  573. // because the new value is not interpolable.
  574. // Note that we also do the latter set of work in
  575. // nsTransitionManager::PruneCompletedTransitions.
  576. if (aElementTransitions) {
  577. bool checkProperties =
  578. aDisp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties;
  579. nsCSSPropertyIDSet allTransitionProperties;
  580. if (checkProperties) {
  581. for (uint32_t i = aDisp->mTransitionPropertyCount; i-- != 0; ) {
  582. const StyleTransition& t = aDisp->mTransitions[i];
  583. // FIXME: Would be good to find a way to share code between this
  584. // interpretation of transition-property and the one above.
  585. nsCSSPropertyID property = t.GetProperty();
  586. if (property == eCSSPropertyExtra_no_properties ||
  587. property == eCSSPropertyExtra_variable ||
  588. property == eCSSProperty_UNKNOWN) {
  589. // Nothing to do, but need to exclude this from cases below.
  590. } else if (property == eCSSPropertyExtra_all_properties) {
  591. for (nsCSSPropertyID p = nsCSSPropertyID(0);
  592. p < eCSSProperty_COUNT_no_shorthands;
  593. p = nsCSSPropertyID(p + 1)) {
  594. allTransitionProperties.AddProperty(p);
  595. }
  596. } else if (nsCSSProps::IsShorthand(property)) {
  597. CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
  598. subprop, property, CSSEnabledState::eForAllContent) {
  599. allTransitionProperties.AddProperty(*subprop);
  600. }
  601. } else {
  602. allTransitionProperties.AddProperty(property);
  603. }
  604. }
  605. }
  606. OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
  607. size_t i = animations.Length();
  608. MOZ_ASSERT(i != 0, "empty transitions list?");
  609. StyleAnimationValue currentValue;
  610. do {
  611. --i;
  612. CSSTransition* anim = animations[i];
  613. // properties no longer in 'transition-property'
  614. if ((checkProperties &&
  615. !allTransitionProperties.HasProperty(anim->TransitionProperty())) ||
  616. // properties whose computed values changed but for which we
  617. // did not start a new transition (because delay and
  618. // duration are both zero, or because the new value is not
  619. // interpolable); a new transition would have anim->ToValue()
  620. // matching currentValue
  621. !ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
  622. aNewStyleContext, currentValue) ||
  623. currentValue != anim->ToValue()) {
  624. // stop the transition
  625. if (anim->HasCurrentEffect()) {
  626. EffectSet* effectSet =
  627. EffectSet::GetEffectSet(aElement,
  628. aNewStyleContext->GetPseudoType());
  629. if (effectSet) {
  630. effectSet->UpdateAnimationGeneration(mPresContext);
  631. }
  632. }
  633. anim->CancelFromStyle();
  634. animations.RemoveElementAt(i);
  635. }
  636. } while (i != 0);
  637. if (animations.IsEmpty()) {
  638. aElementTransitions->Destroy();
  639. aElementTransitions = nullptr;
  640. }
  641. }
  642. return startedAny;
  643. }
  644. void
  645. nsTransitionManager::ConsiderInitiatingTransition(
  646. nsCSSPropertyID aProperty,
  647. const StyleTransition& aTransition,
  648. dom::Element* aElement,
  649. CSSTransitionCollection*& aElementTransitions,
  650. nsStyleContext* aOldStyleContext,
  651. nsStyleContext* aNewStyleContext,
  652. bool* aStartedAny,
  653. nsCSSPropertyIDSet* aWhichStarted)
  654. {
  655. // IsShorthand itself will assert if aProperty is not a property.
  656. MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
  657. "property out of range");
  658. NS_ASSERTION(!aElementTransitions ||
  659. aElementTransitions->mElement == aElement, "Element mismatch");
  660. // Ignore disabled properties. We can arrive here if the transition-property
  661. // is 'all' and the disabled property has a default value which derives value
  662. // from another property, e.g. color.
  663. if (!nsCSSProps::IsEnabled(aProperty, CSSEnabledState::eForAllContent)) {
  664. return;
  665. }
  666. if (aWhichStarted->HasProperty(aProperty)) {
  667. // A later item in transition-property already started a
  668. // transition for this property, so we ignore this one.
  669. // See comment above and
  670. // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
  671. return;
  672. }
  673. if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
  674. return;
  675. }
  676. dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
  677. StyleAnimationValue startValue, endValue, dummyValue;
  678. bool haveValues =
  679. ExtractNonDiscreteComputedValue(aProperty, aOldStyleContext, startValue) &&
  680. ExtractNonDiscreteComputedValue(aProperty, aNewStyleContext, endValue);
  681. bool haveChange = startValue != endValue;
  682. bool shouldAnimate =
  683. haveValues &&
  684. haveChange &&
  685. // Check that we can interpolate between these values
  686. // (If this is ever a performance problem, we could add a
  687. // CanInterpolate method, but it seems fine for now.)
  688. StyleAnimationValue::Interpolate(aProperty, startValue, endValue,
  689. 0.5, dummyValue);
  690. bool haveCurrentTransition = false;
  691. size_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
  692. const ElementPropertyTransition *oldPT = nullptr;
  693. if (aElementTransitions) {
  694. OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
  695. for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
  696. if (animations[i]->TransitionProperty() == aProperty) {
  697. haveCurrentTransition = true;
  698. currentIndex = i;
  699. oldPT = animations[i]->GetEffect()
  700. ? animations[i]->GetEffect()->AsTransition()
  701. : nullptr;
  702. break;
  703. }
  704. }
  705. }
  706. // If we got a style change that changed the value to the endpoint
  707. // of the currently running transition, we don't want to interrupt
  708. // its timing function.
  709. // This needs to be before the !shouldAnimate && haveCurrentTransition
  710. // case below because we might be close enough to the end of the
  711. // transition that the current value rounds to the final value. In
  712. // this case, we'll end up with shouldAnimate as false (because
  713. // there's no value change), but we need to return early here rather
  714. // than cancel the running transition because shouldAnimate is false!
  715. //
  716. // Likewise, if we got a style change that changed the value to the
  717. // endpoint of our finished transition, we also don't want to start
  718. // a new transition for the reasons described in
  719. // https://lists.w3.org/Archives/Public/www-style/2015Jan/0444.html .
  720. if (haveCurrentTransition && haveValues &&
  721. aElementTransitions->mAnimations[currentIndex]->ToValue() == endValue) {
  722. // GetAnimationRule already called RestyleForAnimation.
  723. return;
  724. }
  725. if (!shouldAnimate) {
  726. if (haveCurrentTransition) {
  727. // We're in the middle of a transition, and just got a non-transition
  728. // style change to something that we can't animate. This might happen
  729. // because we got a non-transition style change changing to the current
  730. // in-progress value (which is particularly easy to cause when we're
  731. // currently in the 'transition-delay'). It also might happen because we
  732. // just got a style change to a value that can't be interpolated.
  733. OwningCSSTransitionPtrArray& animations =
  734. aElementTransitions->mAnimations;
  735. animations[currentIndex]->CancelFromStyle();
  736. oldPT = nullptr; // Clear pointer so it doesn't dangle
  737. animations.RemoveElementAt(currentIndex);
  738. EffectSet* effectSet =
  739. EffectSet::GetEffectSet(aElement, aNewStyleContext->GetPseudoType());
  740. if (effectSet) {
  741. effectSet->UpdateAnimationGeneration(mPresContext);
  742. }
  743. if (animations.IsEmpty()) {
  744. aElementTransitions->Destroy();
  745. // |aElementTransitions| is now a dangling pointer!
  746. aElementTransitions = nullptr;
  747. }
  748. // GetAnimationRule already called RestyleForAnimation.
  749. }
  750. return;
  751. }
  752. const nsTimingFunction &tf = aTransition.GetTimingFunction();
  753. float delay = aTransition.GetDelay();
  754. float duration = aTransition.GetDuration();
  755. if (duration < 0.0) {
  756. // The spec says a negative duration is treated as zero.
  757. duration = 0.0;
  758. }
  759. StyleAnimationValue startForReversingTest = startValue;
  760. double reversePortion = 1.0;
  761. // If the new transition reverses an existing one, we'll need to
  762. // handle the timing differently.
  763. // FIXME: Move mStartForReversingTest, mReversePortion to CSSTransition,
  764. // and set the timing function on transitions as an effect-level
  765. // easing (rather than keyframe-level easing). (Bug 1292001)
  766. if (haveCurrentTransition &&
  767. aElementTransitions->mAnimations[currentIndex]->HasCurrentEffect() &&
  768. oldPT &&
  769. oldPT->mStartForReversingTest == endValue) {
  770. // Compute the appropriate negative transition-delay such that right
  771. // now we'd end up at the current position.
  772. double valuePortion =
  773. oldPT->CurrentValuePortion() * oldPT->mReversePortion +
  774. (1.0 - oldPT->mReversePortion);
  775. // A timing function with negative y1 (or y2!) might make
  776. // valuePortion negative. In this case, we still want to apply our
  777. // reversing logic based on relative distances, not make duration
  778. // negative.
  779. if (valuePortion < 0.0) {
  780. valuePortion = -valuePortion;
  781. }
  782. // A timing function with y2 (or y1!) greater than one might
  783. // advance past its terminal value. It's probably a good idea to
  784. // clamp valuePortion to be at most one to preserve the invariant
  785. // that a transition will complete within at most its specified
  786. // time.
  787. if (valuePortion > 1.0) {
  788. valuePortion = 1.0;
  789. }
  790. // Negative delays are essentially part of the transition
  791. // function, so reduce them along with the duration, but don't
  792. // reduce positive delays.
  793. if (delay < 0.0f) {
  794. delay *= valuePortion;
  795. }
  796. duration *= valuePortion;
  797. startForReversingTest = oldPT->ToValue();
  798. reversePortion = valuePortion;
  799. }
  800. TimingParams timing;
  801. timing.mDuration.emplace(StickyTimeDuration::FromMilliseconds(duration));
  802. timing.mDelay = TimeDuration::FromMilliseconds(delay);
  803. timing.mIterations = 1.0;
  804. timing.mDirection = dom::PlaybackDirection::Normal;
  805. timing.mFill = dom::FillMode::Backwards;
  806. // aElement is non-null here, so we emplace it directly.
  807. Maybe<OwningAnimationTarget> target;
  808. target.emplace(aElement, aNewStyleContext->GetPseudoType());
  809. KeyframeEffectParams effectOptions;
  810. RefPtr<ElementPropertyTransition> pt =
  811. new ElementPropertyTransition(aElement->OwnerDoc(), target, timing,
  812. startForReversingTest, reversePortion,
  813. effectOptions);
  814. pt->SetKeyframes(GetTransitionKeyframes(aNewStyleContext, aProperty,
  815. Move(startValue), Move(endValue), tf),
  816. aNewStyleContext);
  817. MOZ_ASSERT(mPresContext->RestyleManager()->IsGecko(),
  818. "ServoRestyleManager should not use nsTransitionManager "
  819. "for transitions");
  820. RefPtr<CSSTransition> animation =
  821. new CSSTransition(mPresContext->Document()->GetScopeObject());
  822. animation->SetOwningElement(
  823. OwningElementRef(*aElement, aNewStyleContext->GetPseudoType()));
  824. animation->SetTimelineNoUpdate(timeline);
  825. animation->SetCreationSequence(
  826. mPresContext->RestyleManager()->AsGecko()->GetAnimationGeneration());
  827. animation->SetEffectFromStyle(pt);
  828. animation->PlayFromStyle();
  829. if (!aElementTransitions) {
  830. bool createdCollection = false;
  831. aElementTransitions =
  832. CSSTransitionCollection::GetOrCreateAnimationCollection(
  833. aElement, aNewStyleContext->GetPseudoType(), &createdCollection);
  834. if (!aElementTransitions) {
  835. MOZ_ASSERT(!createdCollection, "outparam should agree with return value");
  836. NS_WARNING("allocating collection failed");
  837. return;
  838. }
  839. if (createdCollection) {
  840. AddElementCollection(aElementTransitions);
  841. }
  842. }
  843. OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
  844. #ifdef DEBUG
  845. for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
  846. MOZ_ASSERT(
  847. i == currentIndex || animations[i]->TransitionProperty() != aProperty,
  848. "duplicate transitions for property");
  849. }
  850. #endif
  851. if (haveCurrentTransition) {
  852. // If this new transition is replacing an existing transition that is running
  853. // on the compositor, we store select parameters from the replaced transition
  854. // so that later, once all scripts have run, we can update the start value
  855. // of the transition using TimeStamp::Now(). This allows us to avoid a
  856. // large jump when starting a new transition when the main thread lags behind
  857. // the compositor.
  858. if (oldPT &&
  859. oldPT->IsCurrent() &&
  860. oldPT->IsRunningOnCompositor() &&
  861. !oldPT->GetAnimation()->GetStartTime().IsNull() &&
  862. timeline == oldPT->GetAnimation()->GetTimeline()) {
  863. const AnimationPropertySegment& segment =
  864. oldPT->Properties()[0].mSegments[0];
  865. pt->mReplacedTransition.emplace(
  866. ElementPropertyTransition::ReplacedTransitionProperties({
  867. oldPT->GetAnimation()->GetStartTime().Value(),
  868. oldPT->GetAnimation()->PlaybackRate(),
  869. oldPT->SpecifiedTiming(),
  870. segment.mTimingFunction,
  871. segment.mFromValue,
  872. segment.mToValue
  873. })
  874. );
  875. }
  876. animations[currentIndex]->CancelFromStyle();
  877. oldPT = nullptr; // Clear pointer so it doesn't dangle
  878. animations[currentIndex] = animation;
  879. } else {
  880. if (!animations.AppendElement(animation)) {
  881. NS_WARNING("out of memory");
  882. return;
  883. }
  884. }
  885. EffectSet* effectSet =
  886. EffectSet::GetEffectSet(aElement, aNewStyleContext->GetPseudoType());
  887. if (effectSet) {
  888. effectSet->UpdateAnimationGeneration(mPresContext);
  889. }
  890. *aStartedAny = true;
  891. aWhichStarted->AddProperty(aProperty);
  892. }
  893. static Keyframe&
  894. AppendKeyframe(double aOffset, nsCSSPropertyID aProperty,
  895. StyleAnimationValue&& aValue, nsTArray<Keyframe>& aKeyframes)
  896. {
  897. Keyframe& frame = *aKeyframes.AppendElement();
  898. frame.mOffset.emplace(aOffset);
  899. PropertyValuePair& pv = *frame.mPropertyValues.AppendElement();
  900. pv.mProperty = aProperty;
  901. DebugOnly<bool> uncomputeResult =
  902. StyleAnimationValue::UncomputeValue(aProperty, Move(aValue), pv.mValue);
  903. MOZ_ASSERT(uncomputeResult,
  904. "Unable to get specified value from computed value");
  905. return frame;
  906. }
  907. nsTArray<Keyframe>
  908. nsTransitionManager::GetTransitionKeyframes(
  909. nsStyleContext* aStyleContext,
  910. nsCSSPropertyID aProperty,
  911. StyleAnimationValue&& aStartValue,
  912. StyleAnimationValue&& aEndValue,
  913. const nsTimingFunction& aTimingFunction)
  914. {
  915. nsTArray<Keyframe> keyframes(2);
  916. Keyframe& fromFrame = AppendKeyframe(0.0, aProperty, Move(aStartValue),
  917. keyframes);
  918. if (aTimingFunction.mType != nsTimingFunction::Type::Linear) {
  919. fromFrame.mTimingFunction.emplace();
  920. fromFrame.mTimingFunction->Init(aTimingFunction);
  921. }
  922. AppendKeyframe(1.0, aProperty, Move(aEndValue), keyframes);
  923. return keyframes;
  924. }
  925. void
  926. nsTransitionManager::PruneCompletedTransitions(mozilla::dom::Element* aElement,
  927. CSSPseudoElementType aPseudoType,
  928. nsStyleContext* aNewStyleContext)
  929. {
  930. CSSTransitionCollection* collection =
  931. CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
  932. if (!collection) {
  933. return;
  934. }
  935. // Remove any finished transitions whose style doesn't match the new
  936. // style.
  937. // This is similar to some of the work that happens near the end of
  938. // nsTransitionManager::StyleContextChanged.
  939. // FIXME (bug 1158431): Really, we should also cancel running
  940. // transitions whose destination doesn't match as well.
  941. OwningCSSTransitionPtrArray& animations = collection->mAnimations;
  942. size_t i = animations.Length();
  943. MOZ_ASSERT(i != 0, "empty transitions list?");
  944. do {
  945. --i;
  946. CSSTransition* anim = animations[i];
  947. if (anim->HasCurrentEffect()) {
  948. continue;
  949. }
  950. // Since effect is a finished transition, we know it didn't
  951. // influence style.
  952. StyleAnimationValue currentValue;
  953. if (!ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
  954. aNewStyleContext, currentValue) ||
  955. currentValue != anim->ToValue()) {
  956. anim->CancelFromStyle();
  957. animations.RemoveElementAt(i);
  958. }
  959. } while (i != 0);
  960. if (collection->mAnimations.IsEmpty()) {
  961. collection->Destroy();
  962. // |collection| is now a dangling pointer!
  963. collection = nullptr;
  964. }
  965. }
  966. void
  967. nsTransitionManager::StopTransitionsForElement(
  968. mozilla::dom::Element* aElement,
  969. mozilla::CSSPseudoElementType aPseudoType)
  970. {
  971. MOZ_ASSERT(aElement);
  972. CSSTransitionCollection* collection =
  973. CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
  974. if (!collection) {
  975. return;
  976. }
  977. nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
  978. collection->Destroy();
  979. }