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 /*
8  * A struct that represents the value (type and actual data) of an
9  * attribute.
10  */
11 
12 #ifndef nsAttrValue_h___
13 #define nsAttrValue_h___
14 
15 #include <type_traits>
16 
17 #include "nscore.h"
18 #include "nsString.h"
19 #include "nsStringBuffer.h"
20 #include "nsColor.h"
21 #include "nsCaseTreatment.h"
22 #include "nsMargin.h"
23 #include "nsStringFwd.h"
24 #include "nsTArrayForwardDeclare.h"
25 #include "mozilla/AtomArray.h"
26 #include "mozilla/EnumTypeTraits.h"
27 #include "mozilla/MemoryReporting.h"
28 
29 class nsAtom;
30 class nsIPrincipal;
31 class nsIURI;
32 class nsStyledElement;
33 struct MiscContainer;
34 
35 namespace mozilla {
36 class DeclarationBlock;
37 class ShadowParts;
38 class SVGAnimatedIntegerPair;
39 class SVGAnimatedLength;
40 class SVGAnimatedNumberPair;
41 class SVGAnimatedOrient;
42 class SVGAnimatedPreserveAspectRatio;
43 class SVGAnimatedViewBox;
44 class SVGLengthList;
45 class SVGNumberList;
46 class SVGPathData;
47 class SVGPointList;
48 class SVGStringList;
49 class SVGTransformList;
50 
51 namespace dom {
52 class DOMString;
53 }
54 }  // namespace mozilla
55 
56 #define NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM 12
57 
58 const uintptr_t NS_ATTRVALUE_BASETYPE_MASK = 3;
59 #define NS_ATTRVALUE_POINTERVALUE_MASK (~NS_ATTRVALUE_BASETYPE_MASK)
60 
61 #define NS_ATTRVALUE_INTEGERTYPE_BITS 4
62 #define NS_ATTRVALUE_INTEGERTYPE_MASK \
63   (uintptr_t((1 << NS_ATTRVALUE_INTEGERTYPE_BITS) - 1))
64 #define NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER (1 << NS_ATTRVALUE_INTEGERTYPE_BITS)
65 #define NS_ATTRVALUE_INTEGERTYPE_MAXVALUE \
66   ((1 << (31 - NS_ATTRVALUE_INTEGERTYPE_BITS)) - 1)
67 #define NS_ATTRVALUE_INTEGERTYPE_MINVALUE \
68   (-NS_ATTRVALUE_INTEGERTYPE_MAXVALUE - 1)
69 
70 #define NS_ATTRVALUE_ENUMTABLEINDEX_BITS \
71   (32 - 16 - NS_ATTRVALUE_INTEGERTYPE_BITS)
72 #define NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER \
73   (1 << (NS_ATTRVALUE_ENUMTABLEINDEX_BITS - 1))
74 #define NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE \
75   (NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER - 1)
76 #define NS_ATTRVALUE_ENUMTABLEINDEX_MASK                      \
77   (uintptr_t((((1 << NS_ATTRVALUE_ENUMTABLEINDEX_BITS) - 1) & \
78               ~NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER)))
79 
80 /**
81  * A class used to construct a nsString from a nsStringBuffer (we might
82  * want to move this to nsString at some point).
83  *
84  * WARNING: Note that nsCheapString doesn't take an explicit length -- it
85  * assumes the string is maximally large, given the nsStringBuffer's storage
86  * size.  This means the given string buffer *must* be sized exactly correctly
87  * for the string it contains (including one byte for a null terminator).  If
88  * it has any unused storage space, then that will result in bogus characters
89  * at the end of our nsCheapString.
90  */
91 class nsCheapString : public nsString {
92  public:
nsCheapString(nsStringBuffer * aBuf)93   explicit nsCheapString(nsStringBuffer* aBuf) {
94     if (aBuf) aBuf->ToString(aBuf->StorageSize() / sizeof(char16_t) - 1, *this);
95   }
96 };
97 
98 class nsAttrValue {
99   friend struct MiscContainer;
100 
101  public:
102   // This has to be the same as in ValueBaseType
103   enum ValueType {
104     eString = 0x00,   //   00
105                       //   01  this value indicates a 'misc' struct
106     eAtom = 0x02,     //   10
107     eInteger = 0x03,  // 0011
108     eColor = 0x07,    // 0111
109     eEnum = 0x0B,     // 1011  This should eventually die
110     ePercent = 0x0F,  // 1111
111     // Values below here won't matter, they'll be always stored in the 'misc'
112     // struct.
113     eCSSDeclaration = 0x10,
114     eURL,
115     eImage,
116     eAtomArray,
117     eDoubleValue,
118     eIntMarginValue,
119     // eShadowParts is refcounted in the misc container, as we do copy attribute
120     // values quite a bit (for example to process style invalidation), and the
121     // underlying value could get expensive to copy.
122     eShadowParts,
123     eSVGIntegerPair,
124     eSVGTypesBegin = eSVGIntegerPair,
125     eSVGOrient,
126     eSVGLength,
127     eSVGLengthList,
128     eSVGNumberList,
129     eSVGNumberPair,
130     eSVGPathData,
131     eSVGPointList,
132     eSVGPreserveAspectRatio,
133     eSVGStringList,
134     eSVGTransformList,
135     eSVGViewBox,
136     eSVGTypesEnd = eSVGViewBox,
137   };
138 
139   nsAttrValue();
140   nsAttrValue(const nsAttrValue& aOther);
141   explicit nsAttrValue(const nsAString& aValue);
142   explicit nsAttrValue(nsAtom* aValue);
143   nsAttrValue(already_AddRefed<mozilla::DeclarationBlock> aValue,
144               const nsAString* aSerialized);
145   explicit nsAttrValue(const nsIntMargin& aValue);
146   ~nsAttrValue();
147 
148   inline const nsAttrValue& operator=(const nsAttrValue& aOther);
149 
150   static void Init();
151   static void Shutdown();
152 
153   inline ValueType Type() const;
154   // Returns true when this value is self-contained and does not depend on
155   // the state of its associated element.
156   // Returns false when this value depends on the state of its associated
157   // element and may be invalid if that state has been changed by changes to
158   // that element state outside of attribute setting.
159   inline bool StoresOwnData() const;
160 
161   void Reset();
162 
163   void SetTo(const nsAttrValue& aOther);
164   void SetTo(const nsAString& aValue);
165   void SetTo(nsAtom* aValue);
166   void SetTo(int16_t aInt);
167   void SetTo(int32_t aInt, const nsAString* aSerialized);
168   void SetTo(double aValue, const nsAString* aSerialized);
169   void SetTo(already_AddRefed<mozilla::DeclarationBlock> aValue,
170              const nsAString* aSerialized);
171   void SetTo(nsIURI* aValue, const nsAString* aSerialized);
172   void SetTo(const nsIntMargin& aValue);
173   void SetTo(const mozilla::SVGAnimatedIntegerPair& aValue,
174              const nsAString* aSerialized);
175   void SetTo(const mozilla::SVGAnimatedLength& aValue,
176              const nsAString* aSerialized);
177   void SetTo(const mozilla::SVGAnimatedNumberPair& aValue,
178              const nsAString* aSerialized);
179   void SetTo(const mozilla::SVGAnimatedOrient& aValue,
180              const nsAString* aSerialized);
181   void SetTo(const mozilla::SVGAnimatedPreserveAspectRatio& aValue,
182              const nsAString* aSerialized);
183   void SetTo(const mozilla::SVGAnimatedViewBox& aValue,
184              const nsAString* aSerialized);
185   void SetTo(const mozilla::SVGLengthList& aValue,
186              const nsAString* aSerialized);
187   void SetTo(const mozilla::SVGNumberList& aValue,
188              const nsAString* aSerialized);
189   void SetTo(const mozilla::SVGPathData& aValue, const nsAString* aSerialized);
190   void SetTo(const mozilla::SVGPointList& aValue, const nsAString* aSerialized);
191   void SetTo(const mozilla::SVGStringList& aValue,
192              const nsAString* aSerialized);
193   void SetTo(const mozilla::SVGTransformList& aValue,
194              const nsAString* aSerialized);
195 
196   /**
197    * Sets this object with the string or atom representation of aValue.
198    *
199    * After calling this method, this object will have type eString unless the
200    * type of aValue is eAtom, in which case this object will also have type
201    * eAtom.
202    */
203   void SetToSerialized(const nsAttrValue& aValue);
204 
205   void SwapValueWith(nsAttrValue& aOther);
206 
207   void ToString(nsAString& aResult) const;
208   inline void ToString(mozilla::dom::DOMString& aResult) const;
209 
210   /**
211    * Returns the value of this object as an atom. If necessary, the value will
212    * first be serialised using ToString before converting to an atom.
213    */
214   already_AddRefed<nsAtom> GetAsAtom() const;
215 
216   // Methods to get value. These methods do not convert so only use them
217   // to retrieve the datatype that this nsAttrValue has.
218   inline bool IsEmptyString() const;
219   const nsCheapString GetStringValue() const;
220   inline nsAtom* GetAtomValue() const;
221   inline int32_t GetIntegerValue() const;
222   bool GetColorValue(nscolor& aColor) const;
223   inline int16_t GetEnumValue() const;
224   inline double GetPercentValue() const;
225   inline mozilla::AtomArray* GetAtomArrayValue() const;
226   inline mozilla::DeclarationBlock* GetCSSDeclarationValue() const;
227   inline nsIURI* GetURLValue() const;
228   inline double GetDoubleValue() const;
229   bool GetIntMarginValue(nsIntMargin& aMargin) const;
230   inline const mozilla::ShadowParts& GetShadowPartsValue() const;
231 
232   /**
233    * Returns the string corresponding to the stored enum value.
234    *
235    * @param aResult   the string representing the enum tag
236    * @param aRealTag  wheter we want to have the real tag or the saved one
237    */
238   void GetEnumString(nsAString& aResult, bool aRealTag) const;
239 
240   // Methods to get access to atoms we may have
241   // Returns the number of atoms we have; 0 if we have none.  It's OK
242   // to call this without checking the type first; it handles that.
243   uint32_t GetAtomCount() const;
244   // Returns the atom at aIndex (0-based).  Do not call this with
245   // aIndex >= GetAtomCount().
246   nsAtom* AtomAt(int32_t aIndex) const;
247 
248   uint32_t HashValue() const;
249   bool Equals(const nsAttrValue& aOther) const;
250   // aCaseSensitive == eIgnoreCase means ASCII case-insenstive matching
251   bool Equals(const nsAString& aValue, nsCaseTreatment aCaseSensitive) const;
252   bool Equals(const nsAtom* aValue, nsCaseTreatment aCaseSensitive) const;
253   bool HasPrefix(const nsAString& aValue, nsCaseTreatment aCaseSensitive) const;
254   bool HasSuffix(const nsAString& aValue, nsCaseTreatment aCaseSensitive) const;
255   bool HasSubstring(const nsAString& aValue,
256                     nsCaseTreatment aCaseSensitive) const;
257 
258   /**
259    * Compares this object with aOther according to their string representation.
260    *
261    * For example, when called on an object with type eInteger and value 4, and
262    * given aOther of type eString and value "4", EqualsAsStrings will return
263    * true (while Equals will return false).
264    */
265   bool EqualsAsStrings(const nsAttrValue& aOther) const;
266 
267   /**
268    * Returns true if this AttrValue is equal to the given atom, or is an
269    * array which contains the given atom.
270    */
271   bool Contains(nsAtom* aValue, nsCaseTreatment aCaseSensitive) const;
272   /**
273    * Returns true if this AttrValue is an atom equal to the given
274    * string, or is an array of atoms which contains the given string.
275    * This always does a case-sensitive comparison.
276    */
277   bool Contains(const nsAString& aValue) const;
278 
279   void ParseAtom(const nsAString& aValue);
280   void ParseAtomArray(const nsAString& aValue);
281   void ParseStringOrAtom(const nsAString& aValue);
282 
283   /**
284    * Parses an exportparts attribute.
285    *
286    * https://drafts.csswg.org/css-shadow-parts/#parsing-mapping-list
287    */
288   void ParsePartMapping(const nsAString&);
289 
290   /**
291    * Structure for a mapping from int (enum) values to strings.  When you use
292    * it you generally create an array of them.
293    * Instantiate like this:
294    * EnumTable myTable[] = {
295    *   { "string1", 1 },
296    *   { "string2", 2 },
297    *   { nullptr, 0 }
298    * }
299    */
300   struct EnumTable {
301     // EnumTable can be initialized either with an int16_t value
302     // or a value of an enumeration type that can fit within an int16_t.
303 
EnumTableEnumTable304     constexpr EnumTable(const char* aTag, int16_t aValue)
305         : tag(aTag), value(aValue) {}
306 
307     template <typename T,
308               typename = typename std::enable_if<std::is_enum<T>::value>::type>
EnumTableEnumTable309     constexpr EnumTable(const char* aTag, T aValue)
310         : tag(aTag), value(static_cast<int16_t>(aValue)) {
311       static_assert(mozilla::EnumTypeFitsWithin<T, int16_t>::value,
312                     "aValue must be an enum that fits within int16_t");
313     }
314 
315     /** The string the value maps to */
316     const char* tag;
317     /** The enum value that maps to this string */
318     int16_t value;
319   };
320 
321   /**
322    * Parse into an enum value.
323    *
324    * @param aValue the string to find the value for
325    * @param aTable the enumeration to map with
326    * @param aCaseSensitive specify if the parsing has to be case sensitive
327    * @param aDefaultValue if non-null, this function will always return true.
328    *        Failure to parse aValue as one of the values in aTable will just
329    *        cause aDefaultValue->value to be stored as the enumeration value.
330    * @return whether the enum value was found or not
331    */
332   bool ParseEnumValue(const nsAString& aValue, const EnumTable* aTable,
333                       bool aCaseSensitive,
334                       const EnumTable* aDefaultValue = nullptr);
335 
336   /**
337    * Parse a string into a dimension value.  This is similar to
338    * https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values
339    * but drops the fractional part of the value for now, until we figure out how
340    * to store that in our nsAttrValue.
341    *
342    * The resulting value (if the parse succeeds) is one of eInteger,
343    * eDoubleValue, or ePercent, depending on whether we found a fractional part
344    * and whether we found '%' at the end of the value.
345    *
346    * @param aInput the string to parse
347    * @return whether the value could be parsed
348    */
ParseHTMLDimension(const nsAString & aInput)349   bool ParseHTMLDimension(const nsAString& aInput) {
350     return DoParseHTMLDimension(aInput, false);
351   }
352 
353   /**
354    * Parse a string into a nonzero dimension value.  This implements
355    * https://html.spec.whatwg.org/multipage/#rules-for-parsing-non-zero-dimension-values
356    * subject to the same constraints as ParseHTMLDimension above.
357    *
358    * @param aInput the string to parse
359    * @return whether the value could be parsed
360    */
ParseNonzeroHTMLDimension(const nsAString & aInput)361   bool ParseNonzeroHTMLDimension(const nsAString& aInput) {
362     return DoParseHTMLDimension(aInput, true);
363   }
364 
365   /**
366    * Parse a string value into an integer.
367    *
368    * @param aString the string to parse
369    * @return whether the value could be parsed
370    */
ParseIntValue(const nsAString & aString)371   bool ParseIntValue(const nsAString& aString) {
372     return ParseIntWithBounds(aString, INT32_MIN, INT32_MAX);
373   }
374 
375   /**
376    * Parse a string value into an integer with minimum value and maximum value.
377    *
378    * @param aString the string to parse
379    * @param aMin the minimum value (if value is less it will be bumped up)
380    * @param aMax the maximum value (if value is greater it will be chopped down)
381    * @return whether the value could be parsed
382    */
383   bool ParseIntWithBounds(const nsAString& aString, int32_t aMin,
384                           int32_t aMax = INT32_MAX);
385 
386   /**
387    * Parse a string value into an integer with a fallback for invalid values.
388    * Also allows clamping to a maximum value to support col/colgroup.span (this
389    * is not per spec right now).
390    *
391    * @param aString the string to parse
392    * @param aDefault the default value
393    * @param aMax the maximum value (if value is greater it will be clamped)
394    */
395   void ParseIntWithFallback(const nsAString& aString, int32_t aDefault,
396                             int32_t aMax = INT32_MAX);
397 
398   /**
399    * Parse a string value into a non-negative integer.
400    * This method follows the rules for parsing non-negative integer from:
401    * http://dev.w3.org/html5/spec/infrastructure.html#rules-for-parsing-non-negative-integers
402    *
403    * @param  aString the string to parse
404    * @return whether the value is valid
405    */
406   bool ParseNonNegativeIntValue(const nsAString& aString);
407 
408   /**
409    * Parse a string value into a clamped non-negative integer.
410    * This method follows the rules for parsing non-negative integer from:
411    * https://html.spec.whatwg.org/multipage/infrastructure.html#clamped-to-the-range
412    *
413    * @param aString the string to parse
414    * @param aDefault value to return for negative or invalid values
415    * @param aMin minimum value
416    * @param aMax maximum value
417    */
418   void ParseClampedNonNegativeInt(const nsAString& aString, int32_t aDefault,
419                                   int32_t aMin, int32_t aMax);
420 
421   /**
422    * Parse a string value into a positive integer.
423    * This method follows the rules for parsing non-negative integer from:
424    * http://dev.w3.org/html5/spec/infrastructure.html#rules-for-parsing-non-negative-integers
425    * In addition of these rules, the value has to be greater than zero.
426    *
427    * This is generally used for parsing content attributes which reflecting IDL
428    * attributes are limited to only non-negative numbers greater than zero, see:
429    * http://dev.w3.org/html5/spec/common-dom-interfaces.html#limited-to-only-non-negative-numbers-greater-than-zero
430    *
431    * @param aString       the string to parse
432    * @return              whether the value was valid
433    */
434   bool ParsePositiveIntValue(const nsAString& aString);
435 
436   /**
437    * Parse a string into a color.  This implements what HTML5 calls the
438    * "rules for parsing a legacy color value".
439    *
440    * @param aString the string to parse
441    * @return whether the value could be parsed
442    */
443   bool ParseColor(const nsAString& aString);
444 
445   /**
446    * Parse a string value into a double-precision floating point value.
447    *
448    * @param aString the string to parse
449    * @return whether the value could be parsed
450    */
451   bool ParseDoubleValue(const nsAString& aString);
452 
453   /**
454    * Parse a margin string of format 'top, right, bottom, left' into
455    * an nsIntMargin.
456    *
457    * @param aString the string to parse
458    * @return whether the value could be parsed
459    */
460   bool ParseIntMarginValue(const nsAString& aString);
461 
462   /**
463    * Parse a string into a CSS style rule.
464    *
465    * @param aString the style attribute value to be parsed.
466    * @param aElement the element the attribute is set on.
467    * @param aMaybeScriptedPrincipal if available, the scripted principal
468    *        responsible for this attribute value, as passed to
469    *        Element::ParseAttribute.
470    */
471   bool ParseStyleAttribute(const nsAString& aString,
472                            nsIPrincipal* aMaybeScriptedPrincipal,
473                            nsStyledElement* aElement);
474 
475   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
476 
477  private:
478   // These have to be the same as in ValueType
479   enum ValueBaseType {
480     eStringBase = eString,  // 00
481     eOtherBase = 0x01,      // 01
482     eAtomBase = eAtom,      // 10
483     eIntegerBase = 0x03     // 11
484   };
485 
486   inline ValueBaseType BaseType() const;
487   inline bool IsSVGType(ValueType aType) const;
488 
489   /**
490    * Get the index of an EnumTable in the sEnumTableArray.
491    * If the EnumTable is not in the sEnumTableArray, it is added.
492    *
493    * @param aTable   the EnumTable to get the index of.
494    * @return         the index of the EnumTable.
495    */
496   int16_t GetEnumTableIndex(const EnumTable* aTable);
497 
498   inline void SetPtrValueAndType(void* aValue, ValueBaseType aType);
499   void SetIntValueAndType(int32_t aValue, ValueType aType,
500                           const nsAString* aStringValue);
501   // aType can be ePercent or eDoubleValue.
502   void SetDoubleValueAndType(double aValue, ValueType aType,
503                              const nsAString* aStringValue);
504   void SetColorValue(nscolor aColor, const nsAString& aString);
505   void SetMiscAtomOrString(const nsAString* aValue);
506   void ResetMiscAtomOrString();
507   void SetSVGType(ValueType aType, const void* aValue,
508                   const nsAString* aSerialized);
509   inline void ResetIfSet();
510 
511   inline void* GetPtr() const;
512   inline MiscContainer* GetMiscContainer() const;
513   inline int32_t GetIntInternal() const;
514 
515   // Clears the current MiscContainer.  This will return null if there is no
516   // existing container.
517   MiscContainer* ClearMiscContainer();
518   // Like ClearMiscContainer, except allocates a new container if one does not
519   // exist already.
520   MiscContainer* EnsureEmptyMiscContainer();
521   bool EnsureEmptyAtomArray();
522   already_AddRefed<nsStringBuffer> GetStringBuffer(
523       const nsAString& aValue) const;
524   // Given an enum table and a particular entry in that table, return
525   // the actual integer value we should store.
526   int32_t EnumTableEntryToValue(const EnumTable* aEnumTable,
527                                 const EnumTable* aTableEntry);
528 
529   template <typename F>
530   bool SubstringCheck(const nsAString& aValue,
531                       nsCaseTreatment aCaseSensitive) const;
532 
533   static MiscContainer* AllocMiscContainer();
534   static void DeallocMiscContainer(MiscContainer* aCont);
535 
536   static nsTArray<const EnumTable*>* sEnumTableArray;
537   static MiscContainer* sMiscContainerCache;
538 
539   /**
540    * Helper for ParseHTMLDimension and ParseNonzeroHTMLDimension.
541    *
542    * @param aInput the string to parse
543    * @param aEnsureNonzero whether to fail the parse if the value is 0
544    * @return whether the value could be parsed
545    */
546   bool DoParseHTMLDimension(const nsAString& aInput, bool aEnsureNonzero);
547 
548   uintptr_t mBits;
549 };
550 
551 inline const nsAttrValue& nsAttrValue::operator=(const nsAttrValue& aOther) {
552   SetTo(aOther);
553   return *this;
554 }
555 
BaseType()556 inline nsAttrValue::ValueBaseType nsAttrValue::BaseType() const {
557   return static_cast<ValueBaseType>(mBits & NS_ATTRVALUE_BASETYPE_MASK);
558 }
559 
GetPtr()560 inline void* nsAttrValue::GetPtr() const {
561   NS_ASSERTION(BaseType() != eIntegerBase, "getting pointer from non-pointer");
562   return reinterpret_cast<void*>(mBits & NS_ATTRVALUE_POINTERVALUE_MASK);
563 }
564 
IsEmptyString()565 inline bool nsAttrValue::IsEmptyString() const { return !mBits; }
566 
567 #endif
568