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