SMILCompositor.cpp (8830B)
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 #include "SMILCompositor.h" 8 9 #include "SMILCSSProperty.h" 10 #include "mozilla/dom/SVGSVGElement.h" 11 #include "nsCSSProps.h" 12 #include "nsComputedDOMStyle.h" 13 #include "nsHashKeys.h" 14 15 namespace mozilla { 16 17 // PLDHashEntryHdr methods 18 bool SMILCompositor::KeyEquals(KeyTypePointer aKey) const { 19 return aKey && aKey->Equals(mKey); 20 } 21 22 /*static*/ 23 PLDHashNumber SMILCompositor::HashKey(KeyTypePointer aKey) { 24 // Combine the 3 values into one numeric value, which will be hashed. 25 // NOTE: We right-shift one of the pointers by 2 to get some randomness in 26 // its 2 lowest-order bits. (Those shifted-off bits will always be 0 since 27 // our pointers will be word-aligned.) 28 return (NS_PTR_TO_UINT32(aKey->mElement.get()) >> 2) + 29 NS_PTR_TO_UINT32(aKey->mAttributeName.get()); 30 } 31 32 // Cycle-collection support 33 void SMILCompositor::Traverse(nsCycleCollectionTraversalCallback* aCallback) { 34 if (!mKey.mElement) return; 35 36 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "Compositor mKey.mElement"); 37 aCallback->NoteXPCOMChild(mKey.mElement); 38 } 39 40 // Other methods 41 void SMILCompositor::AddAnimationFunction(SMILAnimationFunction* aFunc) { 42 if (aFunc) { 43 #ifdef DEBUG 44 // Check that we don't already have an animation function that references 45 // the animation element used by aFunc. This will be an assert when we 46 // later sort the list. Let's catch the assert now. We use the same 47 // assert message from the sort. 48 for (const SMILAnimationFunction* func : mAnimationFunctions) { 49 MOZ_ASSERT( 50 !aFunc->HasSameAnimationElement(func), 51 "Two animations cannot have the same animation content element!"); 52 } 53 #endif 54 mAnimationFunctions.AppendElement(aFunc); 55 } 56 } 57 58 void SMILCompositor::ComposeAttribute(bool& aMightHavePendingStyleUpdates) { 59 if (!mKey.mElement) return; 60 61 // If we might need to resolve base styles, grab a suitable ComputedStyle 62 // for initializing our SMILAttr with. 63 RefPtr<const ComputedStyle> baseComputedStyle; 64 if (MightNeedBaseStyle()) { 65 baseComputedStyle = nsComputedDOMStyle::GetUnanimatedComputedStyleNoFlush( 66 mKey.mElement, {}); 67 } 68 69 // FIRST: Get the SMILAttr (to grab base value from, and to eventually 70 // give animated value to) 71 UniquePtr<SMILAttr> smilAttr = CreateSMILAttr(baseComputedStyle); 72 if (!smilAttr) { 73 // Target attribute not found (or, out of memory) 74 return; 75 } 76 if (mAnimationFunctions.IsEmpty()) { 77 // No active animation functions. (We can still have a SMILCompositor in 78 // that case if an animation function has *just* become inactive) 79 smilAttr->ClearAnimValue(); 80 // Removing the animation effect may require a style update. 81 aMightHavePendingStyleUpdates = true; 82 return; 83 } 84 85 // SECOND: Sort the animationFunctions, to prepare for compositing. 86 SMILAnimationFunction::Comparator comparator; 87 mAnimationFunctions.Sort(comparator); 88 89 // THIRD: Step backwards through animation functions to find out 90 // which ones we actually care about. 91 uint32_t firstFuncToCompose = GetFirstFuncToAffectSandwich(); 92 93 // FOURTH: Get & cache base value 94 SMILValue sandwichResultValue; 95 if (!mAnimationFunctions[firstFuncToCompose]->WillReplace()) { 96 sandwichResultValue = smilAttr->GetBaseValue(); 97 } 98 UpdateCachedBaseValue(sandwichResultValue); 99 100 if (!mForceCompositing) { 101 return; 102 } 103 104 // FIFTH: Compose animation functions 105 aMightHavePendingStyleUpdates = true; 106 uint32_t length = mAnimationFunctions.Length(); 107 for (uint32_t i = firstFuncToCompose; i < length; ++i) { 108 mAnimationFunctions[i]->ComposeResult(*smilAttr, sandwichResultValue); 109 } 110 if (sandwichResultValue.IsNull()) { 111 smilAttr->ClearAnimValue(); 112 return; 113 } 114 115 // SIXTH: Set the animated value to the final composited result. 116 nsresult rv = smilAttr->SetAnimValue(sandwichResultValue); 117 if (NS_FAILED(rv)) { 118 NS_WARNING("SMILAttr::SetAnimValue failed"); 119 } 120 } 121 122 void SMILCompositor::ClearAnimationEffects() { 123 if (!mKey.mElement || !mKey.mAttributeName) return; 124 125 UniquePtr<SMILAttr> smilAttr = CreateSMILAttr(nullptr); 126 if (!smilAttr) { 127 // Target attribute not found (or, out of memory) 128 return; 129 } 130 smilAttr->ClearAnimValue(); 131 } 132 133 // Protected Helper Functions 134 // -------------------------- 135 UniquePtr<SMILAttr> SMILCompositor::CreateSMILAttr( 136 const ComputedStyle* aBaseComputedStyle) { 137 NonCustomCSSPropertyId propId = GetCSSPropertyToAnimate(); 138 139 if (propId != eCSSProperty_UNKNOWN) { 140 return MakeUnique<SMILCSSProperty>(propId, mKey.mElement.get(), 141 aBaseComputedStyle); 142 } 143 144 return mKey.mElement->GetAnimatedAttr(mKey.mAttributeNamespaceID, 145 mKey.mAttributeName); 146 } 147 148 NonCustomCSSPropertyId SMILCompositor::GetCSSPropertyToAnimate() const { 149 if (mKey.mAttributeNamespaceID != kNameSpaceID_None) { 150 return eCSSProperty_UNKNOWN; 151 } 152 153 NonCustomCSSPropertyId propId = 154 nsCSSProps::LookupProperty(nsAtomCString(mKey.mAttributeName)); 155 156 if (!SMILCSSProperty::IsPropertyAnimatable(propId)) { 157 return eCSSProperty_UNKNOWN; 158 } 159 160 // If we are animating the 'width' or 'height' of an outer SVG 161 // element we should animate it as a CSS property, but for other elements 162 // in SVG namespace (e.g. <rect>) we should animate it as a length attribute. 163 if ((mKey.mAttributeName == nsGkAtoms::width || 164 mKey.mAttributeName == nsGkAtoms::height) && 165 mKey.mElement->GetNameSpaceID() == kNameSpaceID_SVG) { 166 // Not an <svg> element. 167 if (!mKey.mElement->IsSVGElement(nsGkAtoms::svg)) { 168 return eCSSProperty_UNKNOWN; 169 } 170 171 // An inner <svg> element 172 if (static_cast<dom::SVGSVGElement const&>(*mKey.mElement).IsInner()) { 173 return eCSSProperty_UNKNOWN; 174 } 175 176 // Indeed an outer <svg> element, fall through. 177 } 178 179 return propId; 180 } 181 182 bool SMILCompositor::MightNeedBaseStyle() const { 183 if (GetCSSPropertyToAnimate() == eCSSProperty_UNKNOWN) { 184 return false; 185 } 186 187 // We should return true if at least one animation function might build on 188 // the base value. 189 for (const SMILAnimationFunction* func : mAnimationFunctions) { 190 if (!func->WillReplace()) { 191 return true; 192 } 193 } 194 195 return false; 196 } 197 198 uint32_t SMILCompositor::GetFirstFuncToAffectSandwich() { 199 // For performance reasons, we throttle most animations on elements in 200 // display:none subtrees. (We can't throttle animations that target the 201 // "display" property itself, though -- if we did, display:none elements 202 // could never be dynamically displayed via animations.) 203 // To determine whether we're in a display:none subtree, we will check the 204 // element's primary frame since element in display:none subtree doesn't have 205 // a primary frame. Before this process, we will construct frame when we 206 // append an element to subtree. So we will not need to worry about pending 207 // frame construction in this step. 208 bool canThrottle = mKey.mAttributeName != nsGkAtoms::display && 209 !mKey.mElement->GetPrimaryFrame(); 210 211 uint32_t i; 212 for (i = mAnimationFunctions.Length(); i > 0; --i) { 213 SMILAnimationFunction* curAnimFunc = mAnimationFunctions[i - 1]; 214 // In the following, the lack of short-circuit behavior of |= means that we 215 // will ALWAYS run UpdateCachedTarget (even if mForceCompositing is true) 216 // but only call HasChanged and WasSkippedInPrevSample if necessary. This 217 // is important since we need UpdateCachedTarget to run in order to detect 218 // changes to the target in subsequent samples. 219 mForceCompositing |= curAnimFunc->UpdateCachedTarget(mKey) || 220 (curAnimFunc->HasChanged() && !canThrottle) || 221 curAnimFunc->WasSkippedInPrevSample(); 222 223 if (curAnimFunc->WillReplace()) { 224 --i; 225 break; 226 } 227 } 228 229 // Mark remaining animation functions as having been skipped so if we later 230 // use them we'll know to force compositing. 231 // Note that we only really need to do this if something has changed 232 // (otherwise we would have set the flag on a previous sample) and if 233 // something has changed mForceCompositing will be true. 234 if (mForceCompositing) { 235 for (uint32_t j = i; j > 0; --j) { 236 mAnimationFunctions[j - 1]->SetWasSkipped(); 237 } 238 } 239 return i; 240 } 241 242 void SMILCompositor::UpdateCachedBaseValue(const SMILValue& aBaseValue) { 243 if (mCachedBaseValue != aBaseValue) { 244 // Base value has changed since last sample. 245 mCachedBaseValue = aBaseValue; 246 mForceCompositing = true; 247 } 248 } 249 250 } // namespace mozilla