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 "SVGAnimatedLengthList.h"
8 
9 #include <utility>
10 
11 #include "DOMSVGAnimatedLengthList.h"
12 #include "SVGLengthListSMILType.h"
13 #include "mozilla/SMILValue.h"
14 #include "mozilla/dom/SVGElement.h"
15 #include "mozilla/dom/SVGLengthBinding.h"
16 
17 namespace mozilla {
18 
19 using namespace dom;
20 
SetBaseValueString(const nsAString & aValue)21 nsresult SVGAnimatedLengthList::SetBaseValueString(const nsAString& aValue) {
22   SVGLengthList newBaseValue;
23   nsresult rv = newBaseValue.SetValueFromString(aValue);
24   if (NS_FAILED(rv)) {
25     return rv;
26   }
27 
28   DOMSVGAnimatedLengthList* domWrapper =
29       DOMSVGAnimatedLengthList::GetDOMWrapperIfExists(this);
30   if (domWrapper) {
31     // We must send this notification *before* changing mBaseVal! If the length
32     // of our baseVal is being reduced, our baseVal's DOM wrapper list may have
33     // to remove DOM items from itself, and any removed DOM items need to copy
34     // their internal counterpart values *before* we change them.
35     //
36     domWrapper->InternalBaseValListWillChangeTo(newBaseValue);
37   }
38 
39   // We don't need to call DidChange* here - we're only called by
40   // SVGElement::ParseAttribute under Element::SetAttr,
41   // which takes care of notifying.
42 
43   rv = mBaseVal.CopyFrom(newBaseValue);
44   if (NS_FAILED(rv) && domWrapper) {
45     // Attempting to increase mBaseVal's length failed - reduce domWrapper
46     // back to the same length:
47     domWrapper->InternalBaseValListWillChangeTo(mBaseVal);
48   }
49   return rv;
50 }
51 
ClearBaseValue(uint32_t aAttrEnum)52 void SVGAnimatedLengthList::ClearBaseValue(uint32_t aAttrEnum) {
53   DOMSVGAnimatedLengthList* domWrapper =
54       DOMSVGAnimatedLengthList::GetDOMWrapperIfExists(this);
55   if (domWrapper) {
56     // We must send this notification *before* changing mBaseVal! (See above.)
57     domWrapper->InternalBaseValListWillChangeTo(SVGLengthList());
58   }
59   mBaseVal.Clear();
60   // Caller notifies
61 }
62 
SetAnimValue(const SVGLengthList & aNewAnimValue,SVGElement * aElement,uint32_t aAttrEnum)63 nsresult SVGAnimatedLengthList::SetAnimValue(const SVGLengthList& aNewAnimValue,
64                                              SVGElement* aElement,
65                                              uint32_t aAttrEnum) {
66   DOMSVGAnimatedLengthList* domWrapper =
67       DOMSVGAnimatedLengthList::GetDOMWrapperIfExists(this);
68   if (domWrapper) {
69     // A new animation may totally change the number of items in the animVal
70     // list, replacing what was essentially a mirror of the baseVal list, or
71     // else replacing and overriding an existing animation. When this happens
72     // we must try and keep our animVal's DOM wrapper in sync (see the comment
73     // in DOMSVGAnimatedLengthList::InternalBaseValListWillChangeTo).
74     //
75     // It's not possible for us to reliably distinguish between calls to this
76     // method that are setting a new sample for an existing animation, and
77     // calls that are setting the first sample of an animation that will
78     // override an existing animation. Happily it's cheap to just blindly
79     // notify our animVal's DOM wrapper of its internal counterpart's new value
80     // each time this method is called, so that's what we do.
81     //
82     // Note that we must send this notification *before* setting or changing
83     // mAnimVal! (See the comment in SetBaseValueString above.)
84     //
85     domWrapper->InternalAnimValListWillChangeTo(aNewAnimValue);
86   }
87   if (!mAnimVal) {
88     mAnimVal = MakeUnique<SVGLengthList>();
89   }
90   nsresult rv = mAnimVal->CopyFrom(aNewAnimValue);
91   if (NS_FAILED(rv)) {
92     // OOM. We clear the animation, and, importantly, ClearAnimValue() ensures
93     // that mAnimVal and its DOM wrapper (if any) will have the same length!
94     ClearAnimValue(aElement, aAttrEnum);
95     return rv;
96   }
97   aElement->DidAnimateLengthList(aAttrEnum);
98   return NS_OK;
99 }
100 
ClearAnimValue(SVGElement * aElement,uint32_t aAttrEnum)101 void SVGAnimatedLengthList::ClearAnimValue(SVGElement* aElement,
102                                            uint32_t aAttrEnum) {
103   DOMSVGAnimatedLengthList* domWrapper =
104       DOMSVGAnimatedLengthList::GetDOMWrapperIfExists(this);
105   if (domWrapper) {
106     // When all animation ends, animVal simply mirrors baseVal, which may have
107     // a different number of items to the last active animated value. We must
108     // keep the length of our animVal's DOM wrapper list in sync, and again we
109     // must do that before touching mAnimVal. See comments above.
110     //
111     domWrapper->InternalAnimValListWillChangeTo(mBaseVal);
112   }
113   mAnimVal = nullptr;
114   aElement->DidAnimateLengthList(aAttrEnum);
115 }
116 
ToSMILAttr(SVGElement * aSVGElement,uint8_t aAttrEnum,uint8_t aAxis,bool aCanZeroPadList)117 UniquePtr<SMILAttr> SVGAnimatedLengthList::ToSMILAttr(SVGElement* aSVGElement,
118                                                       uint8_t aAttrEnum,
119                                                       uint8_t aAxis,
120                                                       bool aCanZeroPadList) {
121   return MakeUnique<SMILAnimatedLengthList>(this, aSVGElement, aAttrEnum, aAxis,
122                                             aCanZeroPadList);
123 }
124 
ValueFromString(const nsAString & aStr,const dom::SVGAnimationElement *,SMILValue & aValue,bool & aPreventCachingOfSandwich) const125 nsresult SVGAnimatedLengthList::SMILAnimatedLengthList::ValueFromString(
126     const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/,
127     SMILValue& aValue, bool& aPreventCachingOfSandwich) const {
128   SMILValue val(&SVGLengthListSMILType::sSingleton);
129   SVGLengthListAndInfo* llai = static_cast<SVGLengthListAndInfo*>(val.mU.mPtr);
130   nsresult rv = llai->SetValueFromString(aStr);
131   if (NS_SUCCEEDED(rv)) {
132     llai->SetInfo(mElement, mAxis, mCanZeroPadList);
133     aValue = std::move(val);
134 
135     // If any of the lengths in the list depend on their context, then we must
136     // prevent caching of the entire animation sandwich. This is because the
137     // units of a length at a given index can change from sandwich layer to
138     // layer, and indeed even be different within a single sandwich layer. If
139     // any length in the result of an animation sandwich is the result of the
140     // addition of lengths where one or more of those lengths is context
141     // dependent, then naturally the resultant length is also context
142     // dependent, regardless of whether its actual unit is context dependent or
143     // not. Unfortunately normal invalidation mechanisms won't cause us to
144     // recalculate the result of the sandwich if the context changes, so we
145     // take the (substantial) performance hit of preventing caching of the
146     // sandwich layer, causing the animation sandwich to be recalculated every
147     // single sample.
148 
149     aPreventCachingOfSandwich = false;
150     for (uint32_t i = 0; i < llai->Length(); ++i) {
151       uint8_t unit = (*llai)[i].GetUnit();
152       if (unit == SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE ||
153           unit == SVGLength_Binding::SVG_LENGTHTYPE_EMS ||
154           unit == SVGLength_Binding::SVG_LENGTHTYPE_EXS) {
155         aPreventCachingOfSandwich = true;
156         break;
157       }
158     }
159   }
160   return rv;
161 }
162 
GetBaseValue() const163 SMILValue SVGAnimatedLengthList::SMILAnimatedLengthList::GetBaseValue() const {
164   // To benefit from Return Value Optimization and avoid copy constructor calls
165   // due to our use of return-by-value, we must return the exact same object
166   // from ALL return points. This function must only return THIS variable:
167   SMILValue val;
168 
169   SMILValue tmp(&SVGLengthListSMILType::sSingleton);
170   SVGLengthListAndInfo* llai = static_cast<SVGLengthListAndInfo*>(tmp.mU.mPtr);
171   nsresult rv = llai->CopyFrom(mVal->mBaseVal);
172   if (NS_SUCCEEDED(rv)) {
173     llai->SetInfo(mElement, mAxis, mCanZeroPadList);
174     val = std::move(tmp);
175   }
176   return val;
177 }
178 
SetAnimValue(const SMILValue & aValue)179 nsresult SVGAnimatedLengthList::SMILAnimatedLengthList::SetAnimValue(
180     const SMILValue& aValue) {
181   NS_ASSERTION(aValue.mType == &SVGLengthListSMILType::sSingleton,
182                "Unexpected type to assign animated value");
183   if (aValue.mType == &SVGLengthListSMILType::sSingleton) {
184     mVal->SetAnimValue(*static_cast<SVGLengthListAndInfo*>(aValue.mU.mPtr),
185                        mElement, mAttrEnum);
186   }
187   return NS_OK;
188 }
189 
ClearAnimValue()190 void SVGAnimatedLengthList::SMILAnimatedLengthList::ClearAnimValue() {
191   if (mVal->mAnimVal) {
192     mVal->ClearAnimValue(mElement, mAttrEnum);
193   }
194 }
195 
196 }  // namespace mozilla
197