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