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