SVGAnimatedTransformList.cpp (10889B)
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 "SVGAnimatedTransformList.h" 8 9 #include <utility> 10 11 #include "DOMSVGAnimatedTransformList.h" 12 #include "SVGTransform.h" 13 #include "SVGTransformListSMILType.h" 14 #include "mozilla/SMILValue.h" 15 #include "mozilla/SVGContentUtils.h" 16 #include "mozilla/dom/SVGAnimationElement.h" 17 #include "nsCharSeparatedTokenizer.h" 18 #include "nsContentUtils.h" 19 20 using namespace mozilla::dom; 21 using namespace mozilla::dom::SVGTransform_Binding; 22 23 namespace mozilla { 24 25 nsresult SVGAnimatedTransformList::SetBaseValueString(const nsAString& aValue, 26 SVGElement* aSVGElement) { 27 SVGTransformList newBaseValue; 28 nsresult rv = newBaseValue.SetValueFromString(aValue); 29 if (NS_FAILED(rv)) { 30 return rv; 31 } 32 33 return SetBaseValue(newBaseValue, aSVGElement); 34 } 35 36 nsresult SVGAnimatedTransformList::SetBaseValue(const SVGTransformList& aValue, 37 SVGElement* aSVGElement) { 38 DOMSVGAnimatedTransformList* domWrapper = 39 DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this); 40 if (domWrapper) { 41 // We must send this notification *before* changing mBaseVal! If the length 42 // of our baseVal is being reduced, our baseVal's DOM wrapper list may have 43 // to remove DOM items from itself, and any removed DOM items need to copy 44 // their internal counterpart values *before* we change them. 45 // 46 domWrapper->InternalBaseValListWillChangeLengthTo(aValue.Length()); 47 } 48 49 // (This bool will be copied to our member-var, if attr-change succeeds.) 50 bool hadTransform = HasTransform(); 51 52 // We don't need to call DidChange* here - we're only called by 53 // SVGElement::ParseAttribute under Element::SetAttr, 54 // which takes care of notifying. 55 56 nsresult rv = mBaseVal.CopyFrom(aValue); 57 if (NS_FAILED(rv) && domWrapper) { 58 // Attempting to increase mBaseVal's length failed - reduce domWrapper 59 // back to the same length: 60 domWrapper->InternalBaseValListWillChangeLengthTo(mBaseVal.Length()); 61 } else { 62 mIsBaseSet = true; 63 // We only need to treat this as a creation or removal of a transform if the 64 // frame already exists and it didn't have an existing one. 65 mCreatedOrRemovedOnLastChange = 66 aSVGElement->GetPrimaryFrame() && !hadTransform; 67 } 68 return rv; 69 } 70 71 void SVGAnimatedTransformList::ClearBaseValue() { 72 mCreatedOrRemovedOnLastChange = !HasTransform(); 73 74 DOMSVGAnimatedTransformList* domWrapper = 75 DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this); 76 if (domWrapper) { 77 // We must send this notification *before* changing mBaseVal! (See above.) 78 domWrapper->InternalBaseValListWillChangeLengthTo(0); 79 } 80 mBaseVal.Clear(); 81 mIsBaseSet = false; 82 // Caller notifies 83 } 84 85 nsresult SVGAnimatedTransformList::SetAnimValue(const SVGTransformList& aValue, 86 SVGElement* aElement) { 87 bool prevSet = HasTransform() || aElement->GetAnimateMotionTransform(); 88 DOMSVGAnimatedTransformList* domWrapper = 89 DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this); 90 if (domWrapper) { 91 // A new animation may totally change the number of items in the animVal 92 // list, replacing what was essentially a mirror of the baseVal list, or 93 // else replacing and overriding an existing animation. When this happens 94 // we must try and keep our animVal's DOM wrapper in sync (see the comment 95 // in DOMSVGAnimatedTransformList::InternalBaseValListWillChangeLengthTo). 96 // 97 // It's not possible for us to reliably distinguish between calls to this 98 // method that are setting a new sample for an existing animation, and 99 // calls that are setting the first sample of an animation that will 100 // override an existing animation. Happily it's cheap to just blindly 101 // notify our animVal's DOM wrapper of its internal counterpart's new value 102 // each time this method is called, so that's what we do. 103 // 104 // Note that we must send this notification *before* setting or changing 105 // mAnimVal! (See the comment in SetBaseValueString above.) 106 // 107 domWrapper->InternalAnimValListWillChangeLengthTo(aValue.Length()); 108 } 109 if (!mAnimVal) { 110 mAnimVal = MakeUnique<SVGTransformList>(); 111 } 112 nsresult rv = mAnimVal->CopyFrom(aValue); 113 if (NS_FAILED(rv)) { 114 // OOM. We clear the animation, and, importantly, ClearAnimValue() ensures 115 // that mAnimVal and its DOM wrapper (if any) will have the same length! 116 ClearAnimValue(aElement); 117 return rv; 118 } 119 mCreatedOrRemovedOnLastChange = !prevSet; 120 aElement->DidAnimateTransformList(); 121 return NS_OK; 122 } 123 124 void SVGAnimatedTransformList::ClearAnimValue(SVGElement* aElement) { 125 DOMSVGAnimatedTransformList* domWrapper = 126 DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this); 127 if (domWrapper) { 128 // When all animation ends, animVal simply mirrors baseVal, which may have 129 // a different number of items to the last active animated value. We must 130 // keep the length of our animVal's DOM wrapper list in sync, and again we 131 // must do that before touching mAnimVal. See comments above. 132 // 133 domWrapper->InternalAnimValListWillChangeLengthTo(mBaseVal.Length()); 134 } 135 mAnimVal = nullptr; 136 mCreatedOrRemovedOnLastChange = 137 !HasTransform() && !aElement->GetAnimateMotionTransform(); 138 aElement->DidAnimateTransformList(); 139 } 140 141 bool SVGAnimatedTransformList::IsExplicitlySet() const { 142 // Like other methods of this name, we need to know when a transform value has 143 // been explicitly set. 144 // 145 // There are three ways an animated list can become set: 146 // 1) Markup -- we set mIsBaseSet to true on any successful call to 147 // SetBaseValueString and clear it on ClearBaseValue (as called by 148 // SVGElement::UnsetAttr or a failed SVGElement::ParseAttribute) 149 // 2) DOM call -- simply fetching the baseVal doesn't mean the transform value 150 // has been set. It is set if that baseVal has one or more transforms in 151 // the list. 152 // 3) Animation -- which will cause the mAnimVal member to be allocated 153 return mIsBaseSet || !mBaseVal.IsEmpty() || mAnimVal; 154 } 155 156 UniquePtr<SMILAttr> SVGAnimatedTransformList::ToSMILAttr( 157 SVGElement* aSVGElement) { 158 return MakeUnique<SMILAnimatedTransformList>(this, aSVGElement); 159 } 160 161 nsresult SVGAnimatedTransformList::SMILAnimatedTransformList::ValueFromString( 162 const nsAString& aStr, const dom::SVGAnimationElement* aSrcElement, 163 SMILValue& aValue, bool& aPreventCachingOfSandwich) const { 164 NS_ENSURE_TRUE(aSrcElement, NS_ERROR_FAILURE); 165 MOZ_ASSERT(aValue.IsNull(), 166 "aValue should have been cleared before calling ValueFromString"); 167 168 const nsAttrValue* typeAttr = aSrcElement->GetParsedAttr(nsGkAtoms::type); 169 const nsAtom* transformType = nsGkAtoms::translate; // default val 170 if (typeAttr) { 171 if (typeAttr->Type() != nsAttrValue::eAtom) { 172 // Recognized values of |type| are parsed as an atom -- so if we have 173 // something other than an atom, then we know already our |type| is 174 // invalid. 175 return NS_ERROR_FAILURE; 176 } 177 transformType = typeAttr->GetAtomValue(); 178 } 179 180 ParseValue(aStr, transformType, aValue); 181 return aValue.IsNull() ? NS_ERROR_FAILURE : NS_OK; 182 } 183 184 void SVGAnimatedTransformList::SMILAnimatedTransformList::ParseValue( 185 const nsAString& aSpec, const nsAtom* aTransformType, SMILValue& aResult) { 186 MOZ_ASSERT(aResult.IsNull(), "Unexpected type for SMIL value"); 187 188 static_assert(SVGTransformSMILData::NUM_SIMPLE_PARAMS == 3, 189 "SVGSMILTransform constructor should be expecting array " 190 "with 3 params"); 191 192 float params[3] = {0.f}; 193 int32_t numParsed = ParseParameterList(aSpec, params, 3); 194 uint16_t transformType; 195 196 if (aTransformType == nsGkAtoms::translate) { 197 // tx [ty=0] 198 if (numParsed != 1 && numParsed != 2) return; 199 transformType = SVG_TRANSFORM_TRANSLATE; 200 } else if (aTransformType == nsGkAtoms::scale) { 201 // sx [sy=sx] 202 if (numParsed != 1 && numParsed != 2) return; 203 if (numParsed == 1) { 204 params[1] = params[0]; 205 } 206 transformType = SVG_TRANSFORM_SCALE; 207 } else if (aTransformType == nsGkAtoms::rotate) { 208 // r [cx=0 cy=0] 209 if (numParsed != 1 && numParsed != 3) return; 210 transformType = SVG_TRANSFORM_ROTATE; 211 } else if (aTransformType == nsGkAtoms::skewX) { 212 // x-angle 213 if (numParsed != 1) return; 214 transformType = SVG_TRANSFORM_SKEWX; 215 } else if (aTransformType == nsGkAtoms::skewY) { 216 // y-angle 217 if (numParsed != 1) return; 218 transformType = SVG_TRANSFORM_SKEWY; 219 } else { 220 return; 221 } 222 223 SMILValue val(SVGTransformListSMILType::Singleton()); 224 SVGTransformSMILData transform(transformType, params); 225 if (NS_FAILED(SVGTransformListSMILType::AppendTransform(transform, val))) { 226 return; // OOM 227 } 228 229 // Success! Populate our outparam with parsed value. 230 aResult = std::move(val); 231 } 232 233 int32_t SVGAnimatedTransformList::SMILAnimatedTransformList::ParseParameterList( 234 const nsAString& aSpec, float* aVars, int32_t aNVars) { 235 int numArgsFound = 0; 236 237 for (const auto& token : 238 nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace, 239 nsTokenizerFlags::SeparatorOptional>( 240 aSpec, ',') 241 .ToRange()) { 242 float f; 243 if (!SVGContentUtils::ParseNumber(token, f)) { 244 return -1; 245 } 246 if (numArgsFound < aNVars) { 247 aVars[numArgsFound] = f; 248 } 249 numArgsFound++; 250 } 251 return numArgsFound; 252 } 253 254 SMILValue SVGAnimatedTransformList::SMILAnimatedTransformList::GetBaseValue() 255 const { 256 // To benefit from Return Value Optimization and avoid copy constructor calls 257 // due to our use of return-by-value, we must return the exact same object 258 // from ALL return points. This function must only return THIS variable: 259 SMILValue val(SVGTransformListSMILType::Singleton()); 260 if (!SVGTransformListSMILType::AppendTransforms(mVal->mBaseVal, val)) { 261 val = SMILValue(); 262 } 263 264 return val; 265 } 266 267 nsresult SVGAnimatedTransformList::SMILAnimatedTransformList::SetAnimValue( 268 const SMILValue& aNewAnimValue) { 269 MOZ_ASSERT(aNewAnimValue.mType == SVGTransformListSMILType::Singleton(), 270 "Unexpected type to assign animated value"); 271 SVGTransformList animVal; 272 if (!SVGTransformListSMILType::GetTransforms(aNewAnimValue, animVal.mItems)) { 273 return NS_ERROR_FAILURE; 274 } 275 276 return mVal->SetAnimValue(animVal, mElement); 277 } 278 279 void SVGAnimatedTransformList::SMILAnimatedTransformList::ClearAnimValue() { 280 if (mVal->mAnimVal) { 281 mVal->ClearAnimValue(mElement); 282 } 283 } 284 285 } // namespace mozilla