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 #ifndef DOM_SVG_SVGLENGTHLIST_H_
8 #define DOM_SVG_SVGLENGTHLIST_H_
9 
10 #include "nsCOMPtr.h"
11 #include "nsDebug.h"
12 #include "nsIContent.h"
13 #include "nsINode.h"
14 #include "nsIWeakReferenceUtils.h"
15 #include "SVGElement.h"
16 #include "nsTArray.h"
17 #include "SVGLength.h"
18 #include "mozilla/dom/SVGLengthBinding.h"
19 
20 namespace mozilla {
21 
22 namespace dom {
23 class DOMSVGLength;
24 class DOMSVGLengthList;
25 }  // namespace dom
26 
27 /**
28  * ATTENTION! WARNING! WATCH OUT!!
29  *
30  * Consumers that modify objects of this type absolutely MUST keep the DOM
31  * wrappers for those lists (if any) in sync!! That's why this class is so
32  * locked down.
33  *
34  * The DOM wrapper class for this class is DOMSVGLengthList.
35  */
36 class SVGLengthList {
37   friend class dom::DOMSVGLength;
38   friend class dom::DOMSVGLengthList;
39   friend class SVGAnimatedLengthList;
40 
41  public:
42   SVGLengthList() = default;
43   ~SVGLengthList() = default;
44 
45   SVGLengthList& operator=(const SVGLengthList& aOther) {
46     mLengths.ClearAndRetainStorage();
47     // Best-effort, really.
48     Unused << mLengths.AppendElements(aOther.mLengths, fallible);
49     return *this;
50   }
51 
SVGLengthList(const SVGLengthList & aOther)52   SVGLengthList(const SVGLengthList& aOther) { *this = aOther; }
53 
54   // Only methods that don't make/permit modification to this list are public.
55   // Only our friend classes can access methods that may change us.
56 
57   /// This may return an incomplete string on OOM, but that's acceptable.
58   void GetValueAsString(nsAString& aValue) const;
59 
IsEmpty()60   bool IsEmpty() const { return mLengths.IsEmpty(); }
61 
Length()62   uint32_t Length() const { return mLengths.Length(); }
63 
64   const SVGLength& operator[](uint32_t aIndex) const {
65     return mLengths[aIndex];
66   }
67 
68   bool operator==(const SVGLengthList& rhs) const;
69 
SetCapacity(uint32_t size)70   bool SetCapacity(uint32_t size) {
71     return mLengths.SetCapacity(size, fallible);
72   }
73 
Compact()74   void Compact() { mLengths.Compact(); }
75 
76   // Access to methods that can modify objects of this type is deliberately
77   // limited. This is to reduce the chances of someone modifying objects of
78   // this type without taking the necessary steps to keep DOM wrappers in sync.
79   // If you need wider access to these methods, consider adding a method to
80   // SVGAnimatedLengthList and having that class act as an intermediary so it
81   // can take care of keeping DOM wrappers in sync.
82 
83  protected:
84   /**
85    * This may fail on OOM if the internal capacity needs to be increased, in
86    * which case the list will be left unmodified.
87    */
88   nsresult CopyFrom(const SVGLengthList& rhs);
89 
90   SVGLength& operator[](uint32_t aIndex) { return mLengths[aIndex]; }
91 
92   /**
93    * This may fail (return false) on OOM if the internal capacity is being
94    * increased, in which case the list will be left unmodified.
95    */
SetLength(uint32_t aNumberOfItems)96   bool SetLength(uint32_t aNumberOfItems) {
97     return mLengths.SetLength(aNumberOfItems, fallible);
98   }
99 
100  private:
101   // Marking the following private only serves to show which methods are only
102   // used by our friend classes (as opposed to our subclasses) - it doesn't
103   // really provide additional safety.
104 
105   nsresult SetValueFromString(const nsAString& aValue);
106 
Clear()107   void Clear() { mLengths.Clear(); }
108 
InsertItem(uint32_t aIndex,const SVGLength & aLength)109   bool InsertItem(uint32_t aIndex, const SVGLength& aLength) {
110     if (aIndex >= mLengths.Length()) aIndex = mLengths.Length();
111     return !!mLengths.InsertElementAt(aIndex, aLength, fallible);
112   }
113 
ReplaceItem(uint32_t aIndex,const SVGLength & aLength)114   void ReplaceItem(uint32_t aIndex, const SVGLength& aLength) {
115     MOZ_ASSERT(aIndex < mLengths.Length(),
116                "DOM wrapper caller should have raised INDEX_SIZE_ERR");
117     mLengths[aIndex] = aLength;
118   }
119 
RemoveItem(uint32_t aIndex)120   void RemoveItem(uint32_t aIndex) {
121     MOZ_ASSERT(aIndex < mLengths.Length(),
122                "DOM wrapper caller should have raised INDEX_SIZE_ERR");
123     mLengths.RemoveElementAt(aIndex);
124   }
125 
AppendItem(SVGLength aLength)126   bool AppendItem(SVGLength aLength) {
127     return !!mLengths.AppendElement(aLength, fallible);
128   }
129 
130  protected:
131   /* Rationale for using nsTArray<SVGLength> and not nsTArray<SVGLength, 1>:
132    *
133    * It might seem like we should use AutoTArray<SVGLength, 1> instead of
134    * nsTArray<SVGLength>. That would preallocate space for one SVGLength and
135    * avoid an extra memory allocation call in the common case of the 'x'
136    * and 'y' attributes each containing a single length (and the 'dx' and 'dy'
137    * attributes being empty). However, consider this:
138    *
139    * An empty nsTArray uses sizeof(Header*). An AutoTArray<class E,
140    * uint32_t N> on the other hand uses sizeof(Header*) +
141    * (2 * sizeof(uint32_t)) + (N * sizeof(E)), which for one SVGLength is
142    * sizeof(Header*) + 16 bytes.
143    *
144    * Now consider that for text elements we have four length list attributes
145    * (x, y, dx, dy), each of which can have a baseVal and an animVal list. If
146    * we were to go the AutoTArray<SVGLength, 1> route for each of these, we'd
147    * end up using at least 160 bytes for these four attributes alone, even
148    * though we only need storage for two SVGLengths (16 bytes) in the common
149    * case!!
150    *
151    * A compromise might be to template SVGLengthList to allow
152    * SVGAnimatedLengthList to preallocate space for an SVGLength for the
153    * baseVal lists only, and that would cut the space used by the four
154    * attributes to 96 bytes. Taking that even further and templating
155    * SVGAnimatedLengthList too in order to only use nsTArray for 'dx' and 'dy'
156    * would reduce the storage further to 64 bytes. Having different types makes
157    * things more complicated for code that needs to look at the lists though.
158    * In fact it also makes things more complicated when it comes to storing the
159    * lists.
160    *
161    * It may be worth considering using nsAttrValue for length lists instead of
162    * storing them directly on the element.
163    */
164   FallibleTArray<SVGLength> mLengths;
165 };
166 
167 /**
168  * This SVGLengthList subclass is for SVGLengthListSMILType which needs to know
169  * which element and attribute a length list belongs to so that it can convert
170  * between unit types if necessary.
171  */
172 class SVGLengthListAndInfo : public SVGLengthList {
173  public:
SVGLengthListAndInfo()174   SVGLengthListAndInfo()
175       : mElement(nullptr), mAxis(0), mCanZeroPadList(false) {}
176 
SVGLengthListAndInfo(dom::SVGElement * aElement,uint8_t aAxis,bool aCanZeroPadList)177   SVGLengthListAndInfo(dom::SVGElement* aElement, uint8_t aAxis,
178                        bool aCanZeroPadList)
179       : mElement(do_GetWeakReference(static_cast<nsINode*>(aElement))),
180         mAxis(aAxis),
181         mCanZeroPadList(aCanZeroPadList) {}
182 
SetInfo(dom::SVGElement * aElement,uint8_t aAxis,bool aCanZeroPadList)183   void SetInfo(dom::SVGElement* aElement, uint8_t aAxis, bool aCanZeroPadList) {
184     mElement = do_GetWeakReference(static_cast<nsINode*>(aElement));
185     mAxis = aAxis;
186     mCanZeroPadList = aCanZeroPadList;
187   }
188 
Element()189   dom::SVGElement* Element() const {
190     nsCOMPtr<nsIContent> e = do_QueryReferent(mElement);
191     return static_cast<dom::SVGElement*>(e.get());
192   }
193 
194   /**
195    * Returns true if this object is an "identity" value, from the perspective
196    * of SMIL. In other words, returns true until the initial value set up in
197    * SVGLengthListSMILType::Init() has been changed with a SetInfo() call.
198    */
IsIdentity()199   bool IsIdentity() const {
200     if (!mElement) {
201       MOZ_ASSERT(IsEmpty(), "target element propagation failure");
202       return true;
203     }
204     return false;
205   }
206 
Axis()207   uint8_t Axis() const {
208     MOZ_ASSERT(mElement, "Axis() isn't valid");
209     return mAxis;
210   }
211 
212   /**
213    * The value returned by this function depends on which attribute this object
214    * is for. If appending a list of zeros to the attribute's list would have no
215    * effect on rendering (e.g. the attributes 'dx' and 'dy' on <text>), then
216    * this method will return true. If appending a list of zeros to the
217    * attribute's list could *change* rendering (e.g. the attributes 'x' and 'y'
218    * on <text>), then this method will return false.
219    *
220    * The reason that this method exists is because the SMIL code needs to know
221    * what to do when it's asked to animate between lists of different length.
222    * If this method returns true, then it can zero pad the short list before
223    * carrying out its operations. However, in the case of the 'x' and 'y'
224    * attributes on <text>, zero would mean "zero in the current coordinate
225    * system", whereas we would want to pad shorter lists with the coordinates
226    * at which glyphs would otherwise lie, which is almost certainly not zero!
227    * Animating from/to zeros in this case would produce terrible results.
228    *
229    * Currently SVGLengthListSMILType simply disallows (drops) animation between
230    * lists of different length if it can't zero pad a list. This is to avoid
231    * having some authors create content that depends on undesirable behaviour
232    * (which would make it difficult for us to fix the behavior in future). At
233    * some point it would be nice to implement a callback to allow this code to
234    * determine padding values for lists that can't be zero padded. See
235    * https://bugzilla.mozilla.org/show_bug.cgi?id=573431
236    */
CanZeroPadList()237   bool CanZeroPadList() const {
238     // NS_ASSERTION(mElement, "CanZeroPadList() isn't valid");
239     return mCanZeroPadList;
240   }
241 
242   // For the SMIL code. See comment in SVGLengthListSMILType::Add().
SetCanZeroPadList(bool aCanZeroPadList)243   void SetCanZeroPadList(bool aCanZeroPadList) {
244     mCanZeroPadList = aCanZeroPadList;
245   }
246 
CopyFrom(const SVGLengthListAndInfo & rhs)247   nsresult CopyFrom(const SVGLengthListAndInfo& rhs) {
248     mElement = rhs.mElement;
249     mAxis = rhs.mAxis;
250     mCanZeroPadList = rhs.mCanZeroPadList;
251     return SVGLengthList::CopyFrom(rhs);
252   }
253 
254   // Instances of this special subclass do not have DOM wrappers that we need
255   // to worry about keeping in sync, so it's safe to expose any hidden base
256   // class methods required by the SMIL code, as we do below.
257 
258   /**
259    * Exposed so that SVGLengthList baseVals can be copied to
260    * SVGLengthListAndInfo objects. Note that callers should also call
261    * SetInfo() when using this method!
262    */
CopyFrom(const SVGLengthList & rhs)263   nsresult CopyFrom(const SVGLengthList& rhs) {
264     return SVGLengthList::CopyFrom(rhs);
265   }
266   const SVGLength& operator[](uint32_t aIndex) const {
267     return SVGLengthList::operator[](aIndex);
268   }
269   SVGLength& operator[](uint32_t aIndex) {
270     return SVGLengthList::operator[](aIndex);
271   }
SetLength(uint32_t aNumberOfItems)272   bool SetLength(uint32_t aNumberOfItems) {
273     return SVGLengthList::SetLength(aNumberOfItems);
274   }
275 
276  private:
277   // We must keep a weak reference to our element because we may belong to a
278   // cached baseVal SMILValue. See the comments starting at:
279   // https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15
280   // See also https://bugzilla.mozilla.org/show_bug.cgi?id=653497
281   nsWeakPtr mElement;
282   uint8_t mAxis;
283   bool mCanZeroPadList;
284 };
285 
286 /**
287  * This class wraps SVGLengthList objects to allow frame consumers to process
288  * SVGLengthList objects as if they were simply a list of float values in user
289  * units. When consumers request the value at a given index, this class
290  * dynamically converts the corresponding SVGLength from its actual unit and
291  * returns its value in user units.
292  *
293  * Consumers should check that the user unit values returned are finite. Even
294  * if the consumer can guarantee the list's element has a valid viewport
295  * ancestor to resolve percentage units against, and a valid presContext and
296  * ComputedStyle to resolve absolute and em/ex units against, unit conversions
297  * could still overflow. In that case the value returned will be
298  * numeric_limits<float>::quiet_NaN().
299  */
300 class MOZ_STACK_CLASS SVGUserUnitList {
301  public:
SVGUserUnitList()302   SVGUserUnitList() : mList(nullptr), mElement(nullptr), mAxis(0) {}
303 
Init(const SVGLengthList * aList,dom::SVGElement * aElement,uint8_t aAxis)304   void Init(const SVGLengthList* aList, dom::SVGElement* aElement,
305             uint8_t aAxis) {
306     mList = aList;
307     mElement = aElement;
308     mAxis = aAxis;
309   }
310 
Clear()311   void Clear() { mList = nullptr; }
312 
IsEmpty()313   bool IsEmpty() const { return !mList || mList->IsEmpty(); }
314 
Length()315   uint32_t Length() const { return mList ? mList->Length() : 0; }
316 
317   /// This may return a non-finite value
318   float operator[](uint32_t aIndex) const {
319     return (*mList)[aIndex].GetValueInUserUnits(mElement, mAxis);
320   }
321 
HasPercentageValueAt(uint32_t aIndex)322   bool HasPercentageValueAt(uint32_t aIndex) const {
323     const SVGLength& length = (*mList)[aIndex];
324     return length.GetUnit() ==
325            dom::SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE;
326   }
327 
328  private:
329   const SVGLengthList* mList;
330   dom::SVGElement* mElement;
331   uint8_t mAxis;
332 };
333 
334 }  // namespace mozilla
335 
336 #endif  // DOM_SVG_SVGLENGTHLIST_H_
337