AnimationCommon.h (10656B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_css_AnimationCommon_h 8 #define mozilla_css_AnimationCommon_h 9 10 #include "mozilla/AnimationCollection.h" 11 #include "mozilla/Assertions.h" 12 #include "mozilla/LinkedList.h" 13 #include "mozilla/Maybe.h" 14 #include "mozilla/TimingParams.h" 15 #include "mozilla/dom/Animation.h" 16 #include "mozilla/dom/BaseKeyframeTypesBinding.h" 17 #include "mozilla/dom/Nullable.h" 18 #include "nsContentUtils.h" 19 #include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch 20 21 class nsPresContext; 22 23 namespace mozilla { 24 enum class PseudoStyleType : uint8_t; 25 26 namespace dom { 27 class Element; 28 } 29 30 template <class AnimationType> 31 class CommonAnimationManager { 32 public: 33 explicit CommonAnimationManager(nsPresContext* aPresContext) 34 : mPresContext(aPresContext) {} 35 36 // NOTE: This can return null after Disconnect(). 37 nsPresContext* PresContext() const { return mPresContext; } 38 39 /** 40 * Notify the manager that the pres context is going away. 41 */ 42 void Disconnect() { 43 // Content nodes might outlive the transition or animation manager. 44 RemoveAllElementCollections(); 45 46 mPresContext = nullptr; 47 } 48 49 /** 50 * Stop animations on the element. This method takes the real element 51 * rather than the element for the generated content for animations on 52 * ::before, ::after and ::marker. 53 */ 54 void StopAnimationsForElement(dom::Element* aElement, 55 const PseudoStyleRequest& aPseudoRequest) { 56 MOZ_ASSERT(aElement); 57 auto* collection = 58 AnimationCollection<AnimationType>::Get(aElement, aPseudoRequest); 59 if (!collection) { 60 return; 61 } 62 63 nsAutoAnimationMutationBatch mb(aElement->OwnerDoc()); 64 collection->Destroy(); 65 } 66 67 protected: 68 virtual ~CommonAnimationManager() { 69 MOZ_ASSERT(!mPresContext, "Disconnect should have been called"); 70 } 71 72 void AddElementCollection(AnimationCollection<AnimationType>* aCollection) { 73 mElementCollections.insertBack(aCollection); 74 } 75 void RemoveAllElementCollections() { 76 while (AnimationCollection<AnimationType>* head = 77 mElementCollections.getFirst()) { 78 head->Destroy(); // Note: this removes 'head' from mElementCollections. 79 } 80 } 81 82 LinkedList<AnimationCollection<AnimationType>> mElementCollections; 83 nsPresContext* mPresContext; // weak (non-null from ctor to Disconnect) 84 }; 85 86 /** 87 * Utility class for referencing the element that created a CSS animation or 88 * transition. It is non-owning (i.e. it uses a raw pointer) since it is only 89 * expected to be set by the owned animation while it actually being managed 90 * by the owning element. 91 * 92 * This class also abstracts the comparison of an element/pseudo-class pair 93 * for the sake of composite ordering since this logic is common to both CSS 94 * animations and transitions. 95 * 96 * (We call this OwningElementRef instead of just OwningElement so that we can 97 * call the getter on CSSAnimation/CSSTransition OwningElement() without 98 * clashing with this object's contructor.) 99 */ 100 class OwningElementRef final { 101 public: 102 OwningElementRef() = default; 103 104 explicit OwningElementRef(const NonOwningAnimationTarget& aTarget) 105 : mTarget(aTarget) {} 106 107 OwningElementRef(dom::Element& aElement, 108 const PseudoStyleRequest& aPseudoRequest) 109 : mTarget(&aElement, aPseudoRequest) {} 110 111 bool Equals(const OwningElementRef& aOther) const { 112 return mTarget == aOther.mTarget; 113 } 114 115 int32_t Compare(const OwningElementRef& aOther, 116 nsContentUtils::NodeIndexCache& aCache) const { 117 MOZ_ASSERT(mTarget.mElement && aOther.mTarget.mElement, 118 "Elements to compare should not be null"); 119 120 if (mTarget.mElement != aOther.mTarget.mElement) { 121 const bool connected = mTarget.mElement->IsInComposedDoc(); 122 if (connected != aOther.mTarget.mElement->IsInComposedDoc()) { 123 // Disconnected elements sort last. 124 return connected ? -1 : 1; 125 } 126 if (!connected) { 127 auto* thisRoot = mTarget.mElement->SubtreeRoot(); 128 auto* otherRoot = aOther.mTarget.mElement->SubtreeRoot(); 129 if (thisRoot != otherRoot) { 130 // We need some consistent ordering across disconnected subtrees. This 131 // is kind of arbitrary. 132 return uintptr_t(thisRoot) < uintptr_t(otherRoot) ? -1 : 1; 133 } 134 } 135 return nsContentUtils::CompareTreePosition<TreeKind::ShadowIncludingDOM>( 136 mTarget.mElement, aOther.mTarget.mElement, nullptr, &aCache); 137 } 138 139 enum SortingIndex : uint8_t { 140 NotPseudo, 141 Backdrop, 142 Marker, 143 Before, 144 After, 145 ViewTransition, 146 ViewTransitionGroup, 147 ViewTransitionImagePair, 148 ViewTransitionOld, 149 ViewTransitionNew, 150 Other 151 }; 152 auto sortingIndex = 153 [](const PseudoStyleRequest& aPseudoRequest) -> SortingIndex { 154 switch (aPseudoRequest.mType) { 155 case PseudoStyleType::NotPseudo: 156 return SortingIndex::NotPseudo; 157 case PseudoStyleType::backdrop: 158 return SortingIndex::Backdrop; 159 case PseudoStyleType::marker: 160 return SortingIndex::Marker; 161 case PseudoStyleType::before: 162 return SortingIndex::Before; 163 case PseudoStyleType::after: 164 return SortingIndex::After; 165 case PseudoStyleType::viewTransition: 166 return SortingIndex::ViewTransition; 167 case PseudoStyleType::viewTransitionGroup: 168 return SortingIndex::ViewTransitionGroup; 169 case PseudoStyleType::viewTransitionImagePair: 170 return SortingIndex::ViewTransitionImagePair; 171 case PseudoStyleType::viewTransitionOld: 172 return SortingIndex::ViewTransitionOld; 173 case PseudoStyleType::viewTransitionNew: 174 return SortingIndex::ViewTransitionNew; 175 default: 176 MOZ_ASSERT_UNREACHABLE("Unexpected pseudo type"); 177 return SortingIndex::Other; 178 } 179 }; 180 auto cmp = sortingIndex(mTarget.mPseudoRequest) - 181 sortingIndex(aOther.mTarget.mPseudoRequest); 182 if (cmp != 0) { 183 return cmp; 184 } 185 auto* ident = mTarget.mPseudoRequest.mIdentifier.get(); 186 auto* otherIdent = aOther.mTarget.mPseudoRequest.mIdentifier.get(); 187 MOZ_ASSERT(!!ident == !!otherIdent); 188 if (ident == otherIdent) { 189 return 0; 190 } 191 // FIXME(emilio, bug 1956219): This compares ::view-transition-* pseudos 192 // with string comparison, which is not terrible but probably not quite 193 // intended? It seems we should probably compare the pseudo-element tree 194 // position or something if available, at least... 195 return nsDependentAtomString(ident) < nsDependentAtomString(otherIdent) ? -1 196 : 1; 197 } 198 199 bool IsSet() const { return !!mTarget.mElement; } 200 201 bool ShouldFireEvents() const { 202 // NOTE(emilio): Pseudo-elements are represented with a non-native animation 203 // target, and a pseudo-element separately, so the check is also correct for 204 // them. 205 return IsSet() && !mTarget.mElement->IsInNativeAnonymousSubtree(); 206 } 207 208 void GetElement(dom::Element*& aElement, 209 PseudoStyleRequest& aPseudoRequest) const { 210 aElement = mTarget.mElement; 211 aPseudoRequest = mTarget.mPseudoRequest; 212 } 213 214 const NonOwningAnimationTarget& Target() const { return mTarget; } 215 216 nsPresContext* GetPresContext() const { 217 return nsContentUtils::GetContextForContent(mTarget.mElement); 218 } 219 220 private: 221 NonOwningAnimationTarget mTarget; 222 }; 223 224 // Return the TransitionPhase or AnimationPhase to use when the animation 225 // doesn't have a target effect. 226 template <typename PhaseType> 227 PhaseType GetAnimationPhaseWithoutEffect(const dom::Animation& aAnimation) { 228 MOZ_ASSERT(!aAnimation.GetEffect(), 229 "Should only be called when we do not have an effect"); 230 231 dom::Nullable<TimeDuration> currentTime = 232 aAnimation.GetCurrentTimeAsDuration(); 233 if (currentTime.IsNull()) { 234 return PhaseType::Idle; 235 } 236 237 // If we don't have a target effect, the duration will be zero so the phase is 238 // 'before' if the current time is less than zero. 239 return currentTime.Value() < TimeDuration() ? PhaseType::Before 240 : PhaseType::After; 241 }; 242 243 inline dom::PlaybackDirection StyleToDom(StyleAnimationDirection aDirection) { 244 switch (aDirection) { 245 case StyleAnimationDirection::Normal: 246 return dom::PlaybackDirection::Normal; 247 case StyleAnimationDirection::Reverse: 248 return dom::PlaybackDirection::Reverse; 249 case StyleAnimationDirection::Alternate: 250 return dom::PlaybackDirection::Alternate; 251 case StyleAnimationDirection::AlternateReverse: 252 return dom::PlaybackDirection::Alternate_reverse; 253 } 254 MOZ_ASSERT_UNREACHABLE("Wrong style value?"); 255 return dom::PlaybackDirection::Normal; 256 } 257 258 inline dom::FillMode StyleToDom(StyleAnimationFillMode aFillMode) { 259 switch (aFillMode) { 260 case StyleAnimationFillMode::None: 261 return dom::FillMode::None; 262 case StyleAnimationFillMode::Both: 263 return dom::FillMode::Both; 264 case StyleAnimationFillMode::Forwards: 265 return dom::FillMode::Forwards; 266 case StyleAnimationFillMode::Backwards: 267 return dom::FillMode::Backwards; 268 } 269 MOZ_ASSERT_UNREACHABLE("Wrong style value?"); 270 return dom::FillMode::None; 271 } 272 273 inline dom::CompositeOperation StyleToDom(StyleAnimationComposition aStyle) { 274 switch (aStyle) { 275 case StyleAnimationComposition::Replace: 276 return dom::CompositeOperation::Replace; 277 case StyleAnimationComposition::Add: 278 return dom::CompositeOperation::Add; 279 case StyleAnimationComposition::Accumulate: 280 return dom::CompositeOperation::Accumulate; 281 } 282 MOZ_ASSERT_UNREACHABLE("Invalid style composite operation?"); 283 return dom::CompositeOperation::Replace; 284 } 285 286 inline TimingParams TimingParamsFromCSSParams( 287 Maybe<float> aDuration, float aDelay, float aIterationCount, 288 StyleAnimationDirection aDirection, StyleAnimationFillMode aFillMode) { 289 MOZ_ASSERT(aIterationCount >= 0.0 && !std::isnan(aIterationCount), 290 "aIterations should be nonnegative & finite, as ensured by " 291 "CSSParser"); 292 return TimingParams{aDuration, aDelay, aIterationCount, 293 StyleToDom(aDirection), StyleToDom(aFillMode)}; 294 } 295 296 } // namespace mozilla 297 298 #endif /* !defined(mozilla_css_AnimationCommon_h) */