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"
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
;
26 nsresult
SVGAnimatedTransformList::SetBaseValueString(const nsAString
& aValue
,
27 SVGElement
* aSVGElement
) {
28 SVGTransformList newBaseValue
;
29 nsresult rv
= newBaseValue
.SetValueFromString(aValue
);
34 return SetBaseValue(newBaseValue
, aSVGElement
);
37 nsresult
SVGAnimatedTransformList::SetBaseValue(const SVGTransformList
& aValue
,
38 SVGElement
* aSVGElement
) {
39 DOMSVGAnimatedTransformList
* domWrapper
=
40 DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
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());
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
;
72 void SVGAnimatedTransformList::ClearBaseValue() {
73 mCreatedOrRemovedOnLastChange
= !HasTransform();
75 DOMSVGAnimatedTransformList
* domWrapper
=
76 DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
78 // We must send this notification *before* changing mBaseVal! (See above.)
79 domWrapper
->InternalBaseValListWillChangeLengthTo(0);
86 nsresult
SVGAnimatedTransformList::SetAnimValue(const SVGTransformList
& aValue
,
87 SVGElement
* aElement
) {
88 bool prevSet
= HasTransform() || aElement
->GetAnimateMotionTransform();
89 DOMSVGAnimatedTransformList
* domWrapper
=
90 DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
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());
111 mAnimVal
= MakeUnique
<SVGTransformList
>();
113 nsresult rv
= mAnimVal
->CopyFrom(aValue
);
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
);
122 modType
= MutationEvent_Binding::MODIFICATION
;
124 modType
= MutationEvent_Binding::ADDITION
;
126 mCreatedOrRemovedOnLastChange
= modType
== MutationEvent_Binding::ADDITION
;
127 aElement
->DidAnimateTransformList(modType
);
131 void SVGAnimatedTransformList::ClearAnimValue(SVGElement
* aElement
) {
132 DOMSVGAnimatedTransformList
* domWrapper
=
133 DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
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());
144 if (HasTransform() || aElement
->GetAnimateMotionTransform()) {
145 modType
= MutationEvent_Binding::MODIFICATION
;
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
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
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
187 return NS_ERROR_FAILURE
;
189 transformType
= typeAttr
->GetAtomValue();
192 ParseValue(aStr
, transformType
, aValue
);
193 aPreventCachingOfSandwich
= false;
194 return aValue
.IsNull() ? NS_ERROR_FAILURE
: NS_OK
;
197 void SVGAnimatedTransformList::SMILAnimatedTransformList::ParseValue(
198 const nsAString
& aSpec
, const nsAtom
* aTransformType
, SMILValue
& aResult
) {
199 MOZ_ASSERT(aResult
.IsNull(), "Unexpected type for SMIL value");
201 static_assert(SVGTransformSMILData::NUM_SIMPLE_PARAMS
== 3,
202 "SVGSMILTransform constructor should be expecting array "
205 float params
[3] = {0.f
};
206 int32_t numParsed
= ParseParameterList(aSpec
, params
, 3);
207 uint16_t transformType
;
209 if (aTransformType
== nsGkAtoms::translate
) {
211 if (numParsed
!= 1 && numParsed
!= 2) return;
212 transformType
= SVG_TRANSFORM_TRANSLATE
;
213 } else if (aTransformType
== nsGkAtoms::scale
) {
215 if (numParsed
!= 1 && numParsed
!= 2) return;
216 if (numParsed
== 1) {
217 params
[1] = params
[0];
219 transformType
= SVG_TRANSFORM_SCALE
;
220 } else if (aTransformType
== nsGkAtoms::rotate
) {
222 if (numParsed
!= 1 && numParsed
!= 3) return;
223 transformType
= SVG_TRANSFORM_ROTATE
;
224 } else if (aTransformType
== nsGkAtoms::skewX
) {
226 if (numParsed
!= 1) return;
227 transformType
= SVG_TRANSFORM_SKEWX
;
228 } else if (aTransformType
== nsGkAtoms::skewY
) {
230 if (numParsed
!= 1) return;
231 transformType
= SVG_TRANSFORM_SKEWY
;
236 SMILValue
val(SVGTransformListSMILType::Singleton());
237 SVGTransformSMILData
transform(transformType
, params
);
238 if (NS_FAILED(SVGTransformListSMILType::AppendTransform(transform
, val
))) {
242 // Success! Populate our outparam with parsed value.
243 aResult
= std::move(val
);
246 int32_t SVGAnimatedTransformList::SMILAnimatedTransformList::ParseParameterList(
247 const nsAString
& aSpec
, float* aVars
, int32_t aNVars
) {
248 int numArgsFound
= 0;
250 for (const auto& token
:
251 nsCharSeparatedTokenizerTemplate
<nsContentUtils::IsHTMLWhitespace
,
252 nsTokenizerFlags::SeparatorOptional
>(
256 if (!SVGContentUtils::ParseNumber(token
, f
)) {
259 if (numArgsFound
< aNVars
) {
260 aVars
[numArgsFound
] = f
;
267 SMILValue
SVGAnimatedTransformList::SMILAnimatedTransformList::GetBaseValue()
269 // To benefit from Return Value Optimization and avoid copy constructor calls
270 // due to our use of return-by-value, we must return the exact same object
271 // from ALL return points. This function must only return THIS variable:
272 SMILValue
val(SVGTransformListSMILType::Singleton());
273 if (!SVGTransformListSMILType::AppendTransforms(mVal
->mBaseVal
, val
)) {
280 nsresult
SVGAnimatedTransformList::SMILAnimatedTransformList::SetAnimValue(
281 const SMILValue
& aNewAnimValue
) {
282 MOZ_ASSERT(aNewAnimValue
.mType
== SVGTransformListSMILType::Singleton(),
283 "Unexpected type to assign animated value");
284 SVGTransformList animVal
;
285 if (!SVGTransformListSMILType::GetTransforms(aNewAnimValue
, animVal
.mItems
)) {
286 return NS_ERROR_FAILURE
;
289 return mVal
->SetAnimValue(animVal
, mElement
);
292 void SVGAnimatedTransformList::SMILAnimatedTransformList::ClearAnimValue() {
293 if (mVal
->mAnimVal
) {
294 mVal
->ClearAnimValue(mElement
);
298 } // namespace mozilla