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