Bug 1812499 [wpt PR 38184] - Simplify handling of name-to-subdir mapping in canvas...
[gecko.git] / dom / base / nsAttrValue.cpp
blob11e4e72cc8743b2387e03b3cdd2c86b8c6379eb5
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/. */
7 /*
8 * A struct that represents the value (type and actual data) of an
9 * attribute.
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/HashFunctions.h"
15 #include "nsAttrValue.h"
16 #include "nsAttrValueInlines.h"
17 #include "nsAtom.h"
18 #include "nsUnicharUtils.h"
19 #include "mozilla/BloomFilter.h"
20 #include "mozilla/CORSMode.h"
21 #include "mozilla/MemoryReporting.h"
22 #include "mozilla/ServoBindingTypes.h"
23 #include "mozilla/ServoUtils.h"
24 #include "mozilla/ShadowParts.h"
25 #include "mozilla/SVGAttrValueWrapper.h"
26 #include "mozilla/DeclarationBlock.h"
27 #include "mozilla/dom/CSSRuleBinding.h"
28 #include "mozilla/dom/Document.h"
29 #include "nsContentUtils.h"
30 #include "nsReadableUtils.h"
31 #include "nsHTMLCSSStyleSheet.h"
32 #include "nsStyledElement.h"
33 #include "nsIURI.h"
34 #include "ReferrerInfo.h"
35 #include <algorithm>
37 using namespace mozilla;
39 #define MISC_STR_PTR(_cont) \
40 reinterpret_cast<void*>((_cont)->mStringBits & NS_ATTRVALUE_POINTERVALUE_MASK)
42 /* static */
43 MiscContainer* nsAttrValue::AllocMiscContainer() {
44 MOZ_ASSERT(NS_IsMainThread());
45 MiscContainer* cont = nullptr;
46 std::swap(cont, sMiscContainerCache);
48 if (cont) {
49 return new (cont) MiscContainer;
52 return new MiscContainer;
55 /* static */
56 void nsAttrValue::DeallocMiscContainer(MiscContainer* aCont) {
57 MOZ_ASSERT(NS_IsMainThread());
58 if (!aCont) {
59 return;
62 if (!sMiscContainerCache) {
63 aCont->~MiscContainer();
64 sMiscContainerCache = aCont;
65 } else {
66 delete aCont;
70 bool MiscContainer::GetString(nsAString& aString) const {
71 void* ptr = MISC_STR_PTR(this);
73 if (!ptr) {
74 return false;
77 if (static_cast<nsAttrValue::ValueBaseType>(mStringBits &
78 NS_ATTRVALUE_BASETYPE_MASK) ==
79 nsAttrValue::eStringBase) {
80 nsStringBuffer* buffer = static_cast<nsStringBuffer*>(ptr);
81 if (!buffer) {
82 return false;
85 buffer->ToString(buffer->StorageSize() / sizeof(char16_t) - 1, aString);
86 return true;
89 nsAtom* atom = static_cast<nsAtom*>(ptr);
90 if (!atom) {
91 return false;
94 atom->ToString(aString);
95 return true;
98 void MiscContainer::Cache() {
99 // Not implemented for anything else yet.
100 if (mType != nsAttrValue::eCSSDeclaration) {
101 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
102 return;
105 MOZ_ASSERT(IsRefCounted());
106 MOZ_ASSERT(mValue.mRefCount > 0);
107 MOZ_ASSERT(!mValue.mCached);
109 nsHTMLCSSStyleSheet* sheet = mValue.mCSSDeclaration->GetHTMLCSSStyleSheet();
110 if (!sheet) {
111 return;
114 nsString str;
115 bool gotString = GetString(str);
116 if (!gotString) {
117 return;
120 sheet->CacheStyleAttr(str, this);
121 mValue.mCached = 1;
123 // This has to be immutable once it goes into the cache.
124 mValue.mCSSDeclaration->SetImmutable();
127 void MiscContainer::Evict() {
128 // Not implemented for anything else yet.
129 if (mType != nsAttrValue::eCSSDeclaration) {
130 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
131 return;
133 MOZ_ASSERT(IsRefCounted());
134 MOZ_ASSERT(mValue.mRefCount == 0);
136 if (!mValue.mCached) {
137 return;
140 nsHTMLCSSStyleSheet* sheet = mValue.mCSSDeclaration->GetHTMLCSSStyleSheet();
141 MOZ_ASSERT(sheet);
143 nsString str;
144 DebugOnly<bool> gotString = GetString(str);
145 MOZ_ASSERT(gotString);
147 sheet->EvictStyleAttr(str, this);
148 mValue.mCached = 0;
151 nsTArray<const nsAttrValue::EnumTable*>* nsAttrValue::sEnumTableArray = nullptr;
152 MiscContainer* nsAttrValue::sMiscContainerCache = nullptr;
154 nsAttrValue::nsAttrValue() : mBits(0) {}
156 nsAttrValue::nsAttrValue(const nsAttrValue& aOther) : mBits(0) {
157 SetTo(aOther);
160 nsAttrValue::nsAttrValue(const nsAString& aValue) : mBits(0) { SetTo(aValue); }
162 nsAttrValue::nsAttrValue(nsAtom* aValue) : mBits(0) { SetTo(aValue); }
164 nsAttrValue::nsAttrValue(already_AddRefed<DeclarationBlock> aValue,
165 const nsAString* aSerialized)
166 : mBits(0) {
167 SetTo(std::move(aValue), aSerialized);
170 nsAttrValue::nsAttrValue(const nsIntMargin& aValue) : mBits(0) {
171 SetTo(aValue);
174 nsAttrValue::~nsAttrValue() { ResetIfSet(); }
176 /* static */
177 void nsAttrValue::Init() {
178 MOZ_ASSERT(!sEnumTableArray, "nsAttrValue already initialized");
179 sEnumTableArray = new nsTArray<const EnumTable*>;
182 /* static */
183 void nsAttrValue::Shutdown() {
184 MOZ_ASSERT(NS_IsMainThread());
185 delete sEnumTableArray;
186 sEnumTableArray = nullptr;
187 // The MiscContainer pointed to by sMiscContainerCache has already
188 // be destructed so `delete sMiscContainerCache` is
189 // dangerous. Invoke `operator delete` to free the memory.
190 ::operator delete(sMiscContainerCache);
191 sMiscContainerCache = nullptr;
194 void nsAttrValue::Reset() {
195 switch (BaseType()) {
196 case eStringBase: {
197 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
198 if (str) {
199 str->Release();
202 break;
204 case eOtherBase: {
205 MiscContainer* cont = GetMiscContainer();
206 if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) {
207 NS_RELEASE(cont);
208 break;
211 DeallocMiscContainer(ClearMiscContainer());
213 break;
215 case eAtomBase: {
216 nsAtom* atom = GetAtomValue();
217 NS_RELEASE(atom);
219 break;
221 case eIntegerBase: {
222 break;
226 mBits = 0;
229 void nsAttrValue::SetTo(const nsAttrValue& aOther) {
230 if (this == &aOther) {
231 return;
234 switch (aOther.BaseType()) {
235 case eStringBase: {
236 ResetIfSet();
237 nsStringBuffer* str = static_cast<nsStringBuffer*>(aOther.GetPtr());
238 if (str) {
239 str->AddRef();
240 SetPtrValueAndType(str, eStringBase);
242 return;
244 case eOtherBase: {
245 break;
247 case eAtomBase: {
248 ResetIfSet();
249 nsAtom* atom = aOther.GetAtomValue();
250 NS_ADDREF(atom);
251 SetPtrValueAndType(atom, eAtomBase);
252 return;
254 case eIntegerBase: {
255 ResetIfSet();
256 mBits = aOther.mBits;
257 return;
261 MiscContainer* otherCont = aOther.GetMiscContainer();
262 if (otherCont->IsRefCounted()) {
263 DeallocMiscContainer(ClearMiscContainer());
264 NS_ADDREF(otherCont);
265 SetPtrValueAndType(otherCont, eOtherBase);
266 return;
269 MiscContainer* cont = EnsureEmptyMiscContainer();
270 switch (otherCont->mType) {
271 case eInteger: {
272 cont->mValue.mInteger = otherCont->mValue.mInteger;
273 break;
275 case eEnum: {
276 cont->mValue.mEnumValue = otherCont->mValue.mEnumValue;
277 break;
279 case ePercent: {
280 cont->mDoubleValue = otherCont->mDoubleValue;
281 break;
283 case eColor: {
284 cont->mValue.mColor = otherCont->mValue.mColor;
285 break;
287 case eShadowParts:
288 case eCSSDeclaration: {
289 MOZ_CRASH("These should be refcounted!");
291 case eURL: {
292 NS_ADDREF(cont->mValue.mURL = otherCont->mValue.mURL);
293 break;
295 case eAtomArray: {
296 if (!EnsureEmptyAtomArray()) {
297 Reset();
298 return;
300 // XXX(Bug 1631371) Check if this should use a fallible operation as it
301 // pretended earlier.
302 *GetAtomArrayValue() = otherCont->mValue.mAtomArray->Clone();
304 break;
306 case eDoubleValue: {
307 cont->mDoubleValue = otherCont->mDoubleValue;
308 break;
310 case eIntMarginValue: {
311 if (otherCont->mValue.mIntMargin) {
312 cont->mValue.mIntMargin =
313 new nsIntMargin(*otherCont->mValue.mIntMargin);
315 break;
317 default: {
318 if (IsSVGType(otherCont->mType)) {
319 // All SVG types are just pointers to classes and will therefore have
320 // the same size so it doesn't really matter which one we assign
321 cont->mValue.mSVGLength = otherCont->mValue.mSVGLength;
322 } else {
323 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
325 break;
329 void* otherPtr = MISC_STR_PTR(otherCont);
330 if (otherPtr) {
331 if (static_cast<ValueBaseType>(otherCont->mStringBits &
332 NS_ATTRVALUE_BASETYPE_MASK) == eStringBase) {
333 static_cast<nsStringBuffer*>(otherPtr)->AddRef();
334 } else {
335 static_cast<nsAtom*>(otherPtr)->AddRef();
337 cont->SetStringBitsMainThread(otherCont->mStringBits);
339 // Note, set mType after switch-case, otherwise EnsureEmptyAtomArray doesn't
340 // work correctly.
341 cont->mType = otherCont->mType;
344 void nsAttrValue::SetTo(const nsAString& aValue) {
345 ResetIfSet();
346 nsStringBuffer* buf = GetStringBuffer(aValue).take();
347 if (buf) {
348 SetPtrValueAndType(buf, eStringBase);
352 void nsAttrValue::SetTo(nsAtom* aValue) {
353 ResetIfSet();
354 if (aValue) {
355 NS_ADDREF(aValue);
356 SetPtrValueAndType(aValue, eAtomBase);
360 void nsAttrValue::SetTo(int16_t aInt) {
361 ResetIfSet();
362 SetIntValueAndType(aInt, eInteger, nullptr);
365 void nsAttrValue::SetTo(int32_t aInt, const nsAString* aSerialized) {
366 ResetIfSet();
367 SetIntValueAndType(aInt, eInteger, aSerialized);
370 void nsAttrValue::SetTo(double aValue, const nsAString* aSerialized) {
371 MiscContainer* cont = EnsureEmptyMiscContainer();
372 cont->mDoubleValue = aValue;
373 cont->mType = eDoubleValue;
374 SetMiscAtomOrString(aSerialized);
377 void nsAttrValue::SetTo(already_AddRefed<DeclarationBlock> aValue,
378 const nsAString* aSerialized) {
379 MiscContainer* cont = EnsureEmptyMiscContainer();
380 MOZ_ASSERT(cont->mValue.mRefCount == 0);
381 cont->mValue.mCSSDeclaration = aValue.take();
382 cont->mType = eCSSDeclaration;
383 NS_ADDREF(cont);
384 SetMiscAtomOrString(aSerialized);
385 MOZ_ASSERT(cont->mValue.mRefCount == 1);
388 void nsAttrValue::SetTo(nsIURI* aValue, const nsAString* aSerialized) {
389 MiscContainer* cont = EnsureEmptyMiscContainer();
390 NS_ADDREF(cont->mValue.mURL = aValue);
391 cont->mType = eURL;
392 SetMiscAtomOrString(aSerialized);
395 void nsAttrValue::SetTo(const nsIntMargin& aValue) {
396 MiscContainer* cont = EnsureEmptyMiscContainer();
397 cont->mValue.mIntMargin = new nsIntMargin(aValue);
398 cont->mType = eIntMarginValue;
401 void nsAttrValue::SetToSerialized(const nsAttrValue& aOther) {
402 if (aOther.Type() != nsAttrValue::eString &&
403 aOther.Type() != nsAttrValue::eAtom) {
404 nsAutoString val;
405 aOther.ToString(val);
406 SetTo(val);
407 } else {
408 SetTo(aOther);
412 void nsAttrValue::SetTo(const SVGAnimatedOrient& aValue,
413 const nsAString* aSerialized) {
414 SetSVGType(eSVGOrient, &aValue, aSerialized);
417 void nsAttrValue::SetTo(const SVGAnimatedIntegerPair& aValue,
418 const nsAString* aSerialized) {
419 SetSVGType(eSVGIntegerPair, &aValue, aSerialized);
422 void nsAttrValue::SetTo(const SVGAnimatedLength& aValue,
423 const nsAString* aSerialized) {
424 SetSVGType(eSVGLength, &aValue, aSerialized);
427 void nsAttrValue::SetTo(const SVGLengthList& aValue,
428 const nsAString* aSerialized) {
429 // While an empty string will parse as a length list, there's no need to store
430 // it (and SetMiscAtomOrString will assert if we try)
431 if (aSerialized && aSerialized->IsEmpty()) {
432 aSerialized = nullptr;
434 SetSVGType(eSVGLengthList, &aValue, aSerialized);
437 void nsAttrValue::SetTo(const SVGNumberList& aValue,
438 const nsAString* aSerialized) {
439 // While an empty string will parse as a number list, there's no need to store
440 // it (and SetMiscAtomOrString will assert if we try)
441 if (aSerialized && aSerialized->IsEmpty()) {
442 aSerialized = nullptr;
444 SetSVGType(eSVGNumberList, &aValue, aSerialized);
447 void nsAttrValue::SetTo(const SVGAnimatedNumberPair& aValue,
448 const nsAString* aSerialized) {
449 SetSVGType(eSVGNumberPair, &aValue, aSerialized);
452 void nsAttrValue::SetTo(const SVGPathData& aValue,
453 const nsAString* aSerialized) {
454 // While an empty string will parse as path data, there's no need to store it
455 // (and SetMiscAtomOrString will assert if we try)
456 if (aSerialized && aSerialized->IsEmpty()) {
457 aSerialized = nullptr;
459 SetSVGType(eSVGPathData, &aValue, aSerialized);
462 void nsAttrValue::SetTo(const SVGPointList& aValue,
463 const nsAString* aSerialized) {
464 // While an empty string will parse as a point list, there's no need to store
465 // it (and SetMiscAtomOrString will assert if we try)
466 if (aSerialized && aSerialized->IsEmpty()) {
467 aSerialized = nullptr;
469 SetSVGType(eSVGPointList, &aValue, aSerialized);
472 void nsAttrValue::SetTo(const SVGAnimatedPreserveAspectRatio& aValue,
473 const nsAString* aSerialized) {
474 SetSVGType(eSVGPreserveAspectRatio, &aValue, aSerialized);
477 void nsAttrValue::SetTo(const SVGStringList& aValue,
478 const nsAString* aSerialized) {
479 // While an empty string will parse as a string list, there's no need to store
480 // it (and SetMiscAtomOrString will assert if we try)
481 if (aSerialized && aSerialized->IsEmpty()) {
482 aSerialized = nullptr;
484 SetSVGType(eSVGStringList, &aValue, aSerialized);
487 void nsAttrValue::SetTo(const SVGTransformList& aValue,
488 const nsAString* aSerialized) {
489 // While an empty string will parse as a transform list, there's no need to
490 // store it (and SetMiscAtomOrString will assert if we try)
491 if (aSerialized && aSerialized->IsEmpty()) {
492 aSerialized = nullptr;
494 SetSVGType(eSVGTransformList, &aValue, aSerialized);
497 void nsAttrValue::SetTo(const SVGAnimatedViewBox& aValue,
498 const nsAString* aSerialized) {
499 SetSVGType(eSVGViewBox, &aValue, aSerialized);
502 void nsAttrValue::SwapValueWith(nsAttrValue& aOther) {
503 uintptr_t tmp = aOther.mBits;
504 aOther.mBits = mBits;
505 mBits = tmp;
508 void nsAttrValue::ToString(nsAString& aResult) const {
509 MiscContainer* cont = nullptr;
510 if (BaseType() == eOtherBase) {
511 cont = GetMiscContainer();
513 if (cont->GetString(aResult)) {
514 return;
518 switch (Type()) {
519 case eString: {
520 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
521 if (str) {
522 str->ToString(str->StorageSize() / sizeof(char16_t) - 1, aResult);
523 } else {
524 aResult.Truncate();
526 break;
528 case eAtom: {
529 nsAtom* atom = static_cast<nsAtom*>(GetPtr());
530 atom->ToString(aResult);
532 break;
534 case eInteger: {
535 nsAutoString intStr;
536 intStr.AppendInt(GetIntegerValue());
537 aResult = intStr;
539 break;
541 #ifdef DEBUG
542 case eColor: {
543 MOZ_ASSERT_UNREACHABLE("color attribute without string data");
544 aResult.Truncate();
545 break;
547 #endif
548 case eEnum: {
549 GetEnumString(aResult, false);
550 break;
552 case ePercent: {
553 nsAutoString str;
554 if (cont) {
555 str.AppendFloat(cont->mDoubleValue);
556 } else {
557 str.AppendInt(GetIntInternal());
559 aResult = str + u"%"_ns;
561 break;
563 case eCSSDeclaration: {
564 aResult.Truncate();
565 MiscContainer* container = GetMiscContainer();
566 if (DeclarationBlock* decl = container->mValue.mCSSDeclaration) {
567 nsAutoCString result;
568 decl->ToString(result);
569 CopyUTF8toUTF16(result, aResult);
572 // This can be reached during parallel selector matching with attribute
573 // selectors on the style attribute. SetMiscAtomOrString handles this
574 // case, and as of this writing this is the only consumer that needs it.
575 const_cast<nsAttrValue*>(this)->SetMiscAtomOrString(&aResult);
577 break;
579 case eDoubleValue: {
580 aResult.Truncate();
581 aResult.AppendFloat(GetDoubleValue());
582 break;
584 case eSVGIntegerPair: {
585 SVGAttrValueWrapper::ToString(
586 GetMiscContainer()->mValue.mSVGAnimatedIntegerPair, aResult);
587 break;
589 case eSVGOrient: {
590 SVGAttrValueWrapper::ToString(
591 GetMiscContainer()->mValue.mSVGAnimatedOrient, aResult);
592 break;
594 case eSVGLength: {
595 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLength,
596 aResult);
597 break;
599 case eSVGLengthList: {
600 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLengthList,
601 aResult);
602 break;
604 case eSVGNumberList: {
605 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGNumberList,
606 aResult);
607 break;
609 case eSVGNumberPair: {
610 SVGAttrValueWrapper::ToString(
611 GetMiscContainer()->mValue.mSVGAnimatedNumberPair, aResult);
612 break;
614 case eSVGPathData: {
615 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPathData,
616 aResult);
617 break;
619 case eSVGPointList: {
620 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPointList,
621 aResult);
622 break;
624 case eSVGPreserveAspectRatio: {
625 SVGAttrValueWrapper::ToString(
626 GetMiscContainer()->mValue.mSVGAnimatedPreserveAspectRatio, aResult);
627 break;
629 case eSVGStringList: {
630 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGStringList,
631 aResult);
632 break;
634 case eSVGTransformList: {
635 SVGAttrValueWrapper::ToString(
636 GetMiscContainer()->mValue.mSVGTransformList, aResult);
637 break;
639 case eSVGViewBox: {
640 SVGAttrValueWrapper::ToString(
641 GetMiscContainer()->mValue.mSVGAnimatedViewBox, aResult);
642 break;
644 default: {
645 aResult.Truncate();
646 break;
651 already_AddRefed<nsAtom> nsAttrValue::GetAsAtom() const {
652 switch (Type()) {
653 case eString:
654 return NS_AtomizeMainThread(GetStringValue());
656 case eAtom: {
657 RefPtr<nsAtom> atom = GetAtomValue();
658 return atom.forget();
661 default: {
662 nsAutoString val;
663 ToString(val);
664 return NS_AtomizeMainThread(val);
669 const nsCheapString nsAttrValue::GetStringValue() const {
670 MOZ_ASSERT(Type() == eString, "wrong type");
672 return nsCheapString(static_cast<nsStringBuffer*>(GetPtr()));
675 bool nsAttrValue::GetColorValue(nscolor& aColor) const {
676 if (Type() != eColor) {
677 // Unparseable value, treat as unset.
678 NS_ASSERTION(Type() == eString, "unexpected type for color-valued attr");
679 return false;
682 aColor = GetMiscContainer()->mValue.mColor;
683 return true;
686 void nsAttrValue::GetEnumString(nsAString& aResult, bool aRealTag) const {
687 MOZ_ASSERT(Type() == eEnum, "wrong type");
689 uint32_t allEnumBits = (BaseType() == eIntegerBase)
690 ? static_cast<uint32_t>(GetIntInternal())
691 : GetMiscContainer()->mValue.mEnumValue;
692 int16_t val = allEnumBits >> NS_ATTRVALUE_ENUMTABLEINDEX_BITS;
693 const EnumTable* table = sEnumTableArray->ElementAt(
694 allEnumBits & NS_ATTRVALUE_ENUMTABLEINDEX_MASK);
696 while (table->tag) {
697 if (table->value == val) {
698 aResult.AssignASCII(table->tag);
699 if (!aRealTag &&
700 allEnumBits & NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER) {
701 nsContentUtils::ASCIIToUpper(aResult);
703 return;
705 table++;
708 MOZ_ASSERT_UNREACHABLE("couldn't find value in EnumTable");
711 void AttrAtomArray::DoRemoveDuplicates() {
712 MOZ_ASSERT(mMayContainDuplicates);
714 bool usingHashTable = false;
715 BitBloomFilter<8, nsAtom> filter;
716 nsTHashSet<nsPtrHashKey<nsAtom>> hash;
718 auto CheckDuplicate = [&](size_t i) {
719 nsAtom* atom = mArray[i];
720 if (!usingHashTable) {
721 if (!filter.mightContain(atom)) {
722 filter.add(atom);
723 return false;
725 for (size_t j = 0; j < i; ++j) {
726 hash.Insert(mArray[j]);
728 usingHashTable = true;
730 return !hash.EnsureInserted(atom);
733 size_t len = mArray.Length();
734 for (size_t i = 0; i < len; ++i) {
735 if (!CheckDuplicate(i)) {
736 continue;
738 mArray.RemoveElementAt(i);
739 --i;
740 --len;
743 mMayContainDuplicates = false;
746 uint32_t nsAttrValue::GetAtomCount() const {
747 ValueType type = Type();
749 if (type == eAtom) {
750 return 1;
753 if (type == eAtomArray) {
754 return GetAtomArrayValue()->mArray.Length();
757 return 0;
760 nsAtom* nsAttrValue::AtomAt(int32_t aIndex) const {
761 MOZ_ASSERT(aIndex >= 0, "Index must not be negative");
762 MOZ_ASSERT(GetAtomCount() > uint32_t(aIndex), "aIndex out of range");
764 if (BaseType() == eAtomBase) {
765 return GetAtomValue();
768 NS_ASSERTION(Type() == eAtomArray, "GetAtomCount must be confused");
769 return GetAtomArrayValue()->mArray.ElementAt(aIndex);
772 uint32_t nsAttrValue::HashValue() const {
773 switch (BaseType()) {
774 case eStringBase: {
775 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
776 if (str) {
777 uint32_t len = str->StorageSize() / sizeof(char16_t) - 1;
778 return HashString(static_cast<char16_t*>(str->Data()), len);
781 return 0;
783 case eOtherBase: {
784 break;
786 case eAtomBase:
787 case eIntegerBase: {
788 // mBits and uint32_t might have different size. This should silence
789 // any warnings or compile-errors. This is what the implementation of
790 // NS_PTR_TO_INT32 does to take care of the same problem.
791 return mBits - 0;
795 MiscContainer* cont = GetMiscContainer();
796 if (static_cast<ValueBaseType>(cont->mStringBits &
797 NS_ATTRVALUE_BASETYPE_MASK) == eAtomBase) {
798 return cont->mStringBits - 0;
801 switch (cont->mType) {
802 case eInteger: {
803 return cont->mValue.mInteger;
805 case eEnum: {
806 return cont->mValue.mEnumValue;
808 case ePercent: {
809 return cont->mDoubleValue;
811 case eColor: {
812 return cont->mValue.mColor;
814 case eCSSDeclaration: {
815 return NS_PTR_TO_INT32(cont->mValue.mCSSDeclaration);
817 case eURL: {
818 nsString str;
819 ToString(str);
820 return HashString(str);
822 case eAtomArray: {
823 uint32_t hash = 0;
824 for (const auto& atom : cont->mValue.mAtomArray->mArray) {
825 hash = AddToHash(hash, atom.get());
827 return hash;
829 case eDoubleValue: {
830 // XXX this is crappy, but oh well
831 return cont->mDoubleValue;
833 case eIntMarginValue: {
834 return NS_PTR_TO_INT32(cont->mValue.mIntMargin);
836 default: {
837 if (IsSVGType(cont->mType)) {
838 // All SVG types are just pointers to classes so we can treat them alike
839 return NS_PTR_TO_INT32(cont->mValue.mSVGLength);
841 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
842 return 0;
847 bool nsAttrValue::Equals(const nsAttrValue& aOther) const {
848 if (BaseType() != aOther.BaseType()) {
849 return false;
852 switch (BaseType()) {
853 case eStringBase: {
854 return GetStringValue().Equals(aOther.GetStringValue());
856 case eOtherBase: {
857 break;
859 case eAtomBase:
860 case eIntegerBase: {
861 return mBits == aOther.mBits;
865 MiscContainer* thisCont = GetMiscContainer();
866 MiscContainer* otherCont = aOther.GetMiscContainer();
867 if (thisCont == otherCont) {
868 return true;
871 if (thisCont->mType != otherCont->mType) {
872 return false;
875 bool needsStringComparison = false;
877 switch (thisCont->mType) {
878 case eInteger: {
879 if (thisCont->mValue.mInteger == otherCont->mValue.mInteger) {
880 needsStringComparison = true;
882 break;
884 case eEnum: {
885 if (thisCont->mValue.mEnumValue == otherCont->mValue.mEnumValue) {
886 needsStringComparison = true;
888 break;
890 case ePercent: {
891 if (thisCont->mDoubleValue == otherCont->mDoubleValue) {
892 needsStringComparison = true;
894 break;
896 case eColor: {
897 if (thisCont->mValue.mColor == otherCont->mValue.mColor) {
898 needsStringComparison = true;
900 break;
902 case eCSSDeclaration: {
903 return thisCont->mValue.mCSSDeclaration ==
904 otherCont->mValue.mCSSDeclaration;
906 case eURL: {
907 return thisCont->mValue.mURL == otherCont->mValue.mURL;
909 case eAtomArray: {
910 // For classlists we could be insensitive to order, however
911 // classlists are never mapped attributes so they are never compared.
913 if (!(*thisCont->mValue.mAtomArray == *otherCont->mValue.mAtomArray)) {
914 return false;
917 needsStringComparison = true;
918 break;
920 case eDoubleValue: {
921 return thisCont->mDoubleValue == otherCont->mDoubleValue;
923 case eIntMarginValue: {
924 return thisCont->mValue.mIntMargin == otherCont->mValue.mIntMargin;
926 default: {
927 if (IsSVGType(thisCont->mType)) {
928 // Currently this method is never called for nsAttrValue objects that
929 // point to SVG data types.
930 // If that changes then we probably want to add methods to the
931 // corresponding SVG types to compare their base values.
932 // As a shortcut, however, we can begin by comparing the pointers.
933 MOZ_ASSERT(false, "Comparing nsAttrValues that point to SVG data");
934 return false;
936 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
937 return false;
940 if (needsStringComparison) {
941 if (thisCont->mStringBits == otherCont->mStringBits) {
942 return true;
944 if ((static_cast<ValueBaseType>(thisCont->mStringBits &
945 NS_ATTRVALUE_BASETYPE_MASK) ==
946 eStringBase) &&
947 (static_cast<ValueBaseType>(otherCont->mStringBits &
948 NS_ATTRVALUE_BASETYPE_MASK) ==
949 eStringBase)) {
950 return nsCheapString(reinterpret_cast<nsStringBuffer*>(
951 static_cast<uintptr_t>(thisCont->mStringBits)))
952 .Equals(nsCheapString(reinterpret_cast<nsStringBuffer*>(
953 static_cast<uintptr_t>(otherCont->mStringBits))));
956 return false;
959 bool nsAttrValue::Equals(const nsAString& aValue,
960 nsCaseTreatment aCaseSensitive) const {
961 switch (BaseType()) {
962 case eStringBase: {
963 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
964 if (str) {
965 nsDependentString dep(static_cast<char16_t*>(str->Data()),
966 str->StorageSize() / sizeof(char16_t) - 1);
967 return aCaseSensitive == eCaseMatters
968 ? aValue.Equals(dep)
969 : nsContentUtils::EqualsIgnoreASCIICase(aValue, dep);
971 return aValue.IsEmpty();
973 case eAtomBase:
974 if (aCaseSensitive == eCaseMatters) {
975 return static_cast<nsAtom*>(GetPtr())->Equals(aValue);
977 return nsContentUtils::EqualsIgnoreASCIICase(
978 nsDependentAtomString(static_cast<nsAtom*>(GetPtr())), aValue);
979 default:
980 break;
983 nsAutoString val;
984 ToString(val);
985 return aCaseSensitive == eCaseMatters
986 ? val.Equals(aValue)
987 : nsContentUtils::EqualsIgnoreASCIICase(val, aValue);
990 bool nsAttrValue::Equals(const nsAtom* aValue,
991 nsCaseTreatment aCaseSensitive) const {
992 if (aCaseSensitive != eCaseMatters) {
993 // Need a better way to handle this!
994 nsAutoString value;
995 aValue->ToString(value);
996 return Equals(value, aCaseSensitive);
999 switch (BaseType()) {
1000 case eStringBase: {
1001 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
1002 if (str) {
1003 nsDependentString dep(static_cast<char16_t*>(str->Data()),
1004 str->StorageSize() / sizeof(char16_t) - 1);
1005 return aValue->Equals(dep);
1007 return aValue == nsGkAtoms::_empty;
1009 case eAtomBase: {
1010 return static_cast<nsAtom*>(GetPtr()) == aValue;
1012 default:
1013 break;
1016 nsAutoString val;
1017 ToString(val);
1018 return aValue->Equals(val);
1021 struct HasPrefixFn {
1022 static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
1023 const nsAString& aSearchValue,
1024 nsCaseTreatment aCaseSensitive) {
1025 if (aCaseSensitive == eCaseMatters) {
1026 if (aSearchValue.Length() > aAttrLen) {
1027 return false;
1029 return !memcmp(aAttrValue, aSearchValue.BeginReading(),
1030 aSearchValue.Length() * sizeof(char16_t));
1032 return StringBeginsWith(nsDependentString(aAttrValue, aAttrLen),
1033 aSearchValue,
1034 nsASCIICaseInsensitiveStringComparator);
1038 struct HasSuffixFn {
1039 static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
1040 const nsAString& aSearchValue,
1041 nsCaseTreatment aCaseSensitive) {
1042 if (aCaseSensitive == eCaseMatters) {
1043 if (aSearchValue.Length() > aAttrLen) {
1044 return false;
1046 return !memcmp(aAttrValue + aAttrLen - aSearchValue.Length(),
1047 aSearchValue.BeginReading(),
1048 aSearchValue.Length() * sizeof(char16_t));
1050 return StringEndsWith(nsDependentString(aAttrValue, aAttrLen), aSearchValue,
1051 nsASCIICaseInsensitiveStringComparator);
1055 struct HasSubstringFn {
1056 static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
1057 const nsAString& aSearchValue,
1058 nsCaseTreatment aCaseSensitive) {
1059 if (aCaseSensitive == eCaseMatters) {
1060 if (aSearchValue.IsEmpty()) {
1061 return true;
1063 const char16_t* end = aAttrValue + aAttrLen;
1064 return std::search(aAttrValue, end, aSearchValue.BeginReading(),
1065 aSearchValue.EndReading()) != end;
1067 return FindInReadable(aSearchValue, nsDependentString(aAttrValue, aAttrLen),
1068 nsASCIICaseInsensitiveStringComparator);
1072 template <typename F>
1073 bool nsAttrValue::SubstringCheck(const nsAString& aValue,
1074 nsCaseTreatment aCaseSensitive) const {
1075 switch (BaseType()) {
1076 case eStringBase: {
1077 auto str = static_cast<nsStringBuffer*>(GetPtr());
1078 if (str) {
1079 return F::Check(static_cast<char16_t*>(str->Data()),
1080 str->StorageSize() / sizeof(char16_t) - 1, aValue,
1081 aCaseSensitive);
1083 return aValue.IsEmpty();
1085 case eAtomBase: {
1086 auto atom = static_cast<nsAtom*>(GetPtr());
1087 return F::Check(atom->GetUTF16String(), atom->GetLength(), aValue,
1088 aCaseSensitive);
1090 default:
1091 break;
1094 nsAutoString val;
1095 ToString(val);
1096 return F::Check(val.BeginReading(), val.Length(), aValue, aCaseSensitive);
1099 bool nsAttrValue::HasPrefix(const nsAString& aValue,
1100 nsCaseTreatment aCaseSensitive) const {
1101 return SubstringCheck<HasPrefixFn>(aValue, aCaseSensitive);
1104 bool nsAttrValue::HasSuffix(const nsAString& aValue,
1105 nsCaseTreatment aCaseSensitive) const {
1106 return SubstringCheck<HasSuffixFn>(aValue, aCaseSensitive);
1109 bool nsAttrValue::HasSubstring(const nsAString& aValue,
1110 nsCaseTreatment aCaseSensitive) const {
1111 return SubstringCheck<HasSubstringFn>(aValue, aCaseSensitive);
1114 bool nsAttrValue::EqualsAsStrings(const nsAttrValue& aOther) const {
1115 if (Type() == aOther.Type()) {
1116 return Equals(aOther);
1119 // We need to serialize at least one nsAttrValue before passing to
1120 // Equals(const nsAString&), but we can avoid unnecessarily serializing both
1121 // by checking if one is already of a string type.
1122 bool thisIsString = (BaseType() == eStringBase || BaseType() == eAtomBase);
1123 const nsAttrValue& lhs = thisIsString ? *this : aOther;
1124 const nsAttrValue& rhs = thisIsString ? aOther : *this;
1126 switch (rhs.BaseType()) {
1127 case eAtomBase:
1128 return lhs.Equals(rhs.GetAtomValue(), eCaseMatters);
1130 case eStringBase:
1131 return lhs.Equals(rhs.GetStringValue(), eCaseMatters);
1133 default: {
1134 nsAutoString val;
1135 rhs.ToString(val);
1136 return lhs.Equals(val, eCaseMatters);
1141 bool nsAttrValue::Contains(nsAtom* aValue,
1142 nsCaseTreatment aCaseSensitive) const {
1143 switch (BaseType()) {
1144 case eAtomBase: {
1145 nsAtom* atom = GetAtomValue();
1146 if (aCaseSensitive == eCaseMatters) {
1147 return aValue == atom;
1150 // For performance reasons, don't do a full on unicode case insensitive
1151 // string comparison. This is only used for quirks mode anyway.
1152 return nsContentUtils::EqualsIgnoreASCIICase(aValue, atom);
1154 default: {
1155 if (Type() == eAtomArray) {
1156 AttrAtomArray* array = GetAtomArrayValue();
1157 if (aCaseSensitive == eCaseMatters) {
1158 return array->mArray.Contains(aValue);
1161 for (RefPtr<nsAtom>& cur : array->mArray) {
1162 // For performance reasons, don't do a full on unicode case
1163 // insensitive string comparison. This is only used for quirks mode
1164 // anyway.
1165 if (nsContentUtils::EqualsIgnoreASCIICase(aValue, cur)) {
1166 return true;
1173 return false;
1176 struct AtomArrayStringComparator {
1177 bool Equals(nsAtom* atom, const nsAString& string) const {
1178 return atom->Equals(string);
1182 bool nsAttrValue::Contains(const nsAString& aValue) const {
1183 switch (BaseType()) {
1184 case eAtomBase: {
1185 nsAtom* atom = GetAtomValue();
1186 return atom->Equals(aValue);
1188 default: {
1189 if (Type() == eAtomArray) {
1190 AttrAtomArray* array = GetAtomArrayValue();
1191 return array->mArray.Contains(aValue, AtomArrayStringComparator());
1196 return false;
1199 void nsAttrValue::ParseAtom(const nsAString& aValue) {
1200 ResetIfSet();
1202 RefPtr<nsAtom> atom = NS_Atomize(aValue);
1203 if (atom) {
1204 SetPtrValueAndType(atom.forget().take(), eAtomBase);
1208 void nsAttrValue::ParseAtomArray(const nsAString& aValue) {
1209 nsAString::const_iterator iter, end;
1210 aValue.BeginReading(iter);
1211 aValue.EndReading(end);
1212 bool hasSpace = false;
1214 // skip initial whitespace
1215 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1216 hasSpace = true;
1217 ++iter;
1220 if (iter == end) {
1221 SetTo(aValue);
1222 return;
1225 nsAString::const_iterator start(iter);
1227 // get first - and often only - atom
1228 do {
1229 ++iter;
1230 } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));
1232 RefPtr<nsAtom> classAtom = NS_AtomizeMainThread(Substring(start, iter));
1233 if (!classAtom) {
1234 Reset();
1235 return;
1238 // skip whitespace
1239 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1240 hasSpace = true;
1241 ++iter;
1244 if (iter == end && !hasSpace) {
1245 // we only found one classname and there was no whitespace so
1246 // don't bother storing a list
1247 ResetIfSet();
1248 nsAtom* atom = nullptr;
1249 classAtom.swap(atom);
1250 SetPtrValueAndType(atom, eAtomBase);
1251 return;
1254 if (!EnsureEmptyAtomArray()) {
1255 return;
1258 AttrAtomArray* array = GetAtomArrayValue();
1260 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1261 // pretended earlier.
1262 array->mArray.AppendElement(std::move(classAtom));
1264 // parse the rest of the classnames
1265 while (iter != end) {
1266 start = iter;
1268 do {
1269 ++iter;
1270 } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));
1272 classAtom = NS_AtomizeMainThread(Substring(start, iter));
1274 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1275 // pretended earlier.
1276 array->mArray.AppendElement(std::move(classAtom));
1277 array->mMayContainDuplicates = true;
1279 // skip whitespace
1280 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1281 ++iter;
1285 SetMiscAtomOrString(&aValue);
1288 void nsAttrValue::ParseStringOrAtom(const nsAString& aValue) {
1289 uint32_t len = aValue.Length();
1290 // Don't bother with atoms if it's an empty string since
1291 // we can store those efficiently anyway.
1292 if (len && len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) {
1293 ParseAtom(aValue);
1294 } else {
1295 SetTo(aValue);
1299 void nsAttrValue::ParsePartMapping(const nsAString& aValue) {
1300 ResetIfSet();
1301 MiscContainer* cont = EnsureEmptyMiscContainer();
1303 cont->mType = eShadowParts;
1304 cont->mValue.mShadowParts = new ShadowParts(ShadowParts::Parse(aValue));
1305 NS_ADDREF(cont);
1306 SetMiscAtomOrString(&aValue);
1307 MOZ_ASSERT(cont->mValue.mRefCount == 1);
1310 void nsAttrValue::SetIntValueAndType(int32_t aValue, ValueType aType,
1311 const nsAString* aStringValue) {
1312 if (aStringValue || aValue > NS_ATTRVALUE_INTEGERTYPE_MAXVALUE ||
1313 aValue < NS_ATTRVALUE_INTEGERTYPE_MINVALUE) {
1314 MiscContainer* cont = EnsureEmptyMiscContainer();
1315 switch (aType) {
1316 case eInteger: {
1317 cont->mValue.mInteger = aValue;
1318 break;
1320 case ePercent: {
1321 cont->mDoubleValue = aValue;
1322 break;
1324 case eEnum: {
1325 cont->mValue.mEnumValue = aValue;
1326 break;
1328 default: {
1329 MOZ_ASSERT_UNREACHABLE("unknown integer type");
1330 break;
1333 cont->mType = aType;
1334 SetMiscAtomOrString(aStringValue);
1335 } else {
1336 NS_ASSERTION(!mBits, "Reset before calling SetIntValueAndType!");
1337 mBits = (aValue * NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER) | aType;
1341 void nsAttrValue::SetDoubleValueAndType(double aValue, ValueType aType,
1342 const nsAString* aStringValue) {
1343 MOZ_ASSERT(aType == eDoubleValue || aType == ePercent, "Unexpected type");
1344 MiscContainer* cont = EnsureEmptyMiscContainer();
1345 cont->mDoubleValue = aValue;
1346 cont->mType = aType;
1347 SetMiscAtomOrString(aStringValue);
1350 int16_t nsAttrValue::GetEnumTableIndex(const EnumTable* aTable) {
1351 int16_t index = sEnumTableArray->IndexOf(aTable);
1352 if (index < 0) {
1353 index = sEnumTableArray->Length();
1354 NS_ASSERTION(index <= NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE,
1355 "too many enum tables");
1356 sEnumTableArray->AppendElement(aTable);
1359 return index;
1362 int32_t nsAttrValue::EnumTableEntryToValue(const EnumTable* aEnumTable,
1363 const EnumTable* aTableEntry) {
1364 int16_t index = GetEnumTableIndex(aEnumTable);
1365 int32_t value =
1366 (aTableEntry->value << NS_ATTRVALUE_ENUMTABLEINDEX_BITS) + index;
1367 return value;
1370 bool nsAttrValue::ParseEnumValue(const nsAString& aValue,
1371 const EnumTable* aTable, bool aCaseSensitive,
1372 const EnumTable* aDefaultValue) {
1373 ResetIfSet();
1374 const EnumTable* tableEntry = aTable;
1376 while (tableEntry->tag) {
1377 if (aCaseSensitive ? aValue.EqualsASCII(tableEntry->tag)
1378 : aValue.LowerCaseEqualsASCII(tableEntry->tag)) {
1379 int32_t value = EnumTableEntryToValue(aTable, tableEntry);
1381 bool equals = aCaseSensitive || aValue.EqualsASCII(tableEntry->tag);
1382 if (!equals) {
1383 nsAutoString tag;
1384 tag.AssignASCII(tableEntry->tag);
1385 nsContentUtils::ASCIIToUpper(tag);
1386 if ((equals = tag.Equals(aValue))) {
1387 value |= NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER;
1390 SetIntValueAndType(value, eEnum, equals ? nullptr : &aValue);
1391 NS_ASSERTION(GetEnumValue() == tableEntry->value,
1392 "failed to store enum properly");
1394 return true;
1396 tableEntry++;
1399 if (aDefaultValue) {
1400 MOZ_ASSERT(aTable <= aDefaultValue && aDefaultValue < tableEntry,
1401 "aDefaultValue not inside aTable?");
1402 SetIntValueAndType(EnumTableEntryToValue(aTable, aDefaultValue), eEnum,
1403 &aValue);
1404 return true;
1407 return false;
1410 bool nsAttrValue::DoParseHTMLDimension(const nsAString& aInput,
1411 bool aEnsureNonzero) {
1412 ResetIfSet();
1414 // We don't use nsContentUtils::ParseHTMLInteger here because we
1415 // need a bunch of behavioral differences from it. We _could_ try to
1416 // use it, but it would not be a great fit.
1418 // https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values
1420 // Steps 1 and 2.
1421 const char16_t* position = aInput.BeginReading();
1422 const char16_t* end = aInput.EndReading();
1424 // We will need to keep track of whether this was a canonical representation
1425 // or not. It's non-canonical if it has leading whitespace, leading '+',
1426 // leading '0' characters, or trailing garbage.
1427 bool canonical = true;
1429 // Step 3.
1430 while (position != end && nsContentUtils::IsHTMLWhitespace(*position)) {
1431 canonical = false; // Leading whitespace
1432 ++position;
1435 // Step 4.
1436 if (position == end || *position < char16_t('0') ||
1437 *position > char16_t('9')) {
1438 return false;
1441 // Step 5.
1442 CheckedInt32 value = 0;
1444 // Collect up leading '0' first to avoid extra branching in the main
1445 // loop to set 'canonical' properly.
1446 while (position != end && *position == char16_t('0')) {
1447 canonical = false; // Leading '0'
1448 ++position;
1451 // Now collect up other digits.
1452 while (position != end && *position >= char16_t('0') &&
1453 *position <= char16_t('9')) {
1454 value = value * 10 + (*position - char16_t('0'));
1455 if (!value.isValid()) {
1456 // The spec assumes we can deal with arbitrary-size integers here, but we
1457 // really can't. If someone sets something too big, just bail out and
1458 // ignore it.
1459 return false;
1461 ++position;
1464 // Step 6 is implemented implicitly via the various "position != end" guards
1465 // from this point on.
1467 Maybe<double> doubleValue;
1468 // Step 7. The return in step 7.2 is handled by just falling through to the
1469 // code below this block when we reach end of input or a non-digit, because
1470 // the while loop will terminate at that point.
1471 if (position != end && *position == char16_t('.')) {
1472 canonical = false; // Let's not rely on double serialization reproducing
1473 // the string we started with.
1474 // Step 7.1.
1475 ++position;
1476 // If we have a '.' _not_ followed by digits, this is not as efficient as it
1477 // could be, because we will store as a double while we could have stored as
1478 // an int. But that seems like a pretty rare case.
1479 doubleValue.emplace(value.value());
1480 // Step 7.3.
1481 double divisor = 1.0f;
1482 // Step 7.4.
1483 while (position != end && *position >= char16_t('0') &&
1484 *position <= char16_t('9')) {
1485 // Step 7.4.1.
1486 divisor = divisor * 10.0f;
1487 // Step 7.4.2.
1488 doubleValue.ref() += (*position - char16_t('0')) / divisor;
1489 // Step 7.4.3.
1490 ++position;
1491 // Step 7.4.4 and 7.4.5 are captured in the while loop condition and the
1492 // "position != end" checks below.
1496 if (aEnsureNonzero && value.value() == 0 &&
1497 (!doubleValue || *doubleValue == 0.0f)) {
1498 // Not valid. Just drop it.
1499 return false;
1502 // Step 8 and the spec's early return from step 7.2.
1503 ValueType type;
1504 if (position != end && *position == char16_t('%')) {
1505 type = ePercent;
1506 ++position;
1507 } else if (doubleValue) {
1508 type = eDoubleValue;
1509 } else {
1510 type = eInteger;
1513 if (position != end) {
1514 canonical = false;
1517 if (doubleValue) {
1518 MOZ_ASSERT(!canonical, "We set it false above!");
1519 SetDoubleValueAndType(*doubleValue, type, &aInput);
1520 } else {
1521 SetIntValueAndType(value.value(), type, canonical ? nullptr : &aInput);
1524 #ifdef DEBUG
1525 nsAutoString str;
1526 ToString(str);
1527 MOZ_ASSERT(str == aInput, "We messed up our 'canonical' boolean!");
1528 #endif
1530 return true;
1533 bool nsAttrValue::ParseIntWithBounds(const nsAString& aString, int32_t aMin,
1534 int32_t aMax) {
1535 MOZ_ASSERT(aMin < aMax, "bad boundaries");
1537 ResetIfSet();
1539 nsContentUtils::ParseHTMLIntegerResultFlags result;
1540 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1541 if (result & nsContentUtils::eParseHTMLInteger_Error) {
1542 return false;
1545 int32_t val = std::max(originalVal, aMin);
1546 val = std::min(val, aMax);
1547 bool nonStrict =
1548 (val != originalVal) ||
1549 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1550 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1552 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1554 return true;
1557 void nsAttrValue::ParseIntWithFallback(const nsAString& aString,
1558 int32_t aDefault, int32_t aMax) {
1559 ResetIfSet();
1561 nsContentUtils::ParseHTMLIntegerResultFlags result;
1562 int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
1563 bool nonStrict = false;
1564 if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 1) {
1565 val = aDefault;
1566 nonStrict = true;
1569 if (val > aMax) {
1570 val = aMax;
1571 nonStrict = true;
1574 if ((result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1575 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput)) {
1576 nonStrict = true;
1579 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1582 void nsAttrValue::ParseClampedNonNegativeInt(const nsAString& aString,
1583 int32_t aDefault, int32_t aMin,
1584 int32_t aMax) {
1585 ResetIfSet();
1587 nsContentUtils::ParseHTMLIntegerResultFlags result;
1588 int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
1589 bool nonStrict =
1590 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1591 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1593 if (result & nsContentUtils::eParseHTMLInteger_ErrorOverflow) {
1594 if (result & nsContentUtils::eParseHTMLInteger_Negative) {
1595 val = aDefault;
1596 } else {
1597 val = aMax;
1599 nonStrict = true;
1600 } else if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 0) {
1601 val = aDefault;
1602 nonStrict = true;
1603 } else if (val < aMin) {
1604 val = aMin;
1605 nonStrict = true;
1606 } else if (val > aMax) {
1607 val = aMax;
1608 nonStrict = true;
1611 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1614 bool nsAttrValue::ParseNonNegativeIntValue(const nsAString& aString) {
1615 ResetIfSet();
1617 nsContentUtils::ParseHTMLIntegerResultFlags result;
1618 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1619 if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal < 0) {
1620 return false;
1623 bool nonStrict =
1624 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1625 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1627 SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);
1629 return true;
1632 bool nsAttrValue::ParsePositiveIntValue(const nsAString& aString) {
1633 ResetIfSet();
1635 nsContentUtils::ParseHTMLIntegerResultFlags result;
1636 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1637 if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal <= 0) {
1638 return false;
1641 bool nonStrict =
1642 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1643 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1645 SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);
1647 return true;
1650 void nsAttrValue::SetColorValue(nscolor aColor, const nsAString& aString) {
1651 nsStringBuffer* buf = GetStringBuffer(aString).take();
1652 if (!buf) {
1653 return;
1656 MiscContainer* cont = EnsureEmptyMiscContainer();
1657 cont->mValue.mColor = aColor;
1658 cont->mType = eColor;
1660 // Save the literal string we were passed for round-tripping.
1661 cont->SetStringBitsMainThread(reinterpret_cast<uintptr_t>(buf) | eStringBase);
1664 bool nsAttrValue::ParseColor(const nsAString& aString) {
1665 ResetIfSet();
1667 // FIXME (partially, at least): HTML5's algorithm says we shouldn't do
1668 // the whitespace compression, trimming, or the test for emptiness.
1669 // (I'm a little skeptical that we shouldn't do the whitespace
1670 // trimming; WebKit also does it.)
1671 nsAutoString colorStr(aString);
1672 colorStr.CompressWhitespace(true, true);
1673 if (colorStr.IsEmpty()) {
1674 return false;
1677 nscolor color;
1678 // No color names begin with a '#'; in standards mode, all acceptable
1679 // numeric colors do.
1680 if (colorStr.First() == '#') {
1681 nsDependentString withoutHash(colorStr.get() + 1, colorStr.Length() - 1);
1682 if (NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
1683 SetColorValue(color, aString);
1684 return true;
1686 } else {
1687 if (NS_ColorNameToRGB(colorStr, &color)) {
1688 SetColorValue(color, aString);
1689 return true;
1693 // FIXME (maybe): HTML5 says we should handle system colors. This
1694 // means we probably need another storage type, since we'd need to
1695 // handle dynamic changes. However, I think this is a bad idea:
1696 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-May/026449.html
1698 // Use NS_LooseHexToRGB as a fallback if nothing above worked.
1699 if (NS_LooseHexToRGB(colorStr, &color)) {
1700 SetColorValue(color, aString);
1701 return true;
1704 return false;
1707 bool nsAttrValue::ParseDoubleValue(const nsAString& aString) {
1708 ResetIfSet();
1710 nsresult ec;
1711 double val = PromiseFlatString(aString).ToDouble(&ec);
1712 if (NS_FAILED(ec)) {
1713 return false;
1716 MiscContainer* cont = EnsureEmptyMiscContainer();
1717 cont->mDoubleValue = val;
1718 cont->mType = eDoubleValue;
1719 nsAutoString serializedFloat;
1720 serializedFloat.AppendFloat(val);
1721 SetMiscAtomOrString(serializedFloat.Equals(aString) ? nullptr : &aString);
1722 return true;
1725 bool nsAttrValue::ParseIntMarginValue(const nsAString& aString) {
1726 ResetIfSet();
1728 nsIntMargin margins;
1729 if (!nsContentUtils::ParseIntMarginValue(aString, margins)) return false;
1731 MiscContainer* cont = EnsureEmptyMiscContainer();
1732 cont->mValue.mIntMargin = new nsIntMargin(margins);
1733 cont->mType = eIntMarginValue;
1734 SetMiscAtomOrString(&aString);
1735 return true;
1738 bool nsAttrValue::ParseStyleAttribute(const nsAString& aString,
1739 nsIPrincipal* aMaybeScriptedPrincipal,
1740 nsStyledElement* aElement) {
1741 dom::Document* ownerDoc = aElement->OwnerDoc();
1742 nsHTMLCSSStyleSheet* sheet = ownerDoc->GetInlineStyleSheet();
1743 nsIURI* baseURI = aElement->GetBaseURIForStyleAttr();
1744 nsIURI* docURI = ownerDoc->GetDocumentURI();
1746 NS_ASSERTION(aElement->NodePrincipal() == ownerDoc->NodePrincipal(),
1747 "This is unexpected");
1749 nsIPrincipal* principal = aMaybeScriptedPrincipal ? aMaybeScriptedPrincipal
1750 : aElement->NodePrincipal();
1752 // If the (immutable) document URI does not match the element's base URI
1753 // (the common case is that they do match) do not cache the rule. This is
1754 // because the results of the CSS parser are dependent on these URIs, and we
1755 // do not want to have to account for the URIs in the hash lookup.
1756 // Similarly, if the triggering principal does not match the node principal,
1757 // do not cache the rule, since the principal will be encoded in any parsed
1758 // URLs in the rule.
1759 const bool cachingAllowed =
1760 sheet && baseURI == docURI && principal == aElement->NodePrincipal();
1761 if (cachingAllowed) {
1762 MiscContainer* cont = sheet->LookupStyleAttr(aString);
1763 if (cont) {
1764 // Set our MiscContainer to the cached one.
1765 NS_ADDREF(cont);
1766 SetPtrValueAndType(cont, eOtherBase);
1767 return true;
1771 nsCOMPtr<nsIReferrerInfo> referrerInfo =
1772 dom::ReferrerInfo::CreateForInternalCSSResources(ownerDoc);
1773 auto data = MakeRefPtr<URLExtraData>(baseURI, referrerInfo, principal);
1774 RefPtr<DeclarationBlock> decl = DeclarationBlock::FromCssText(
1775 aString, data, ownerDoc->GetCompatibilityMode(), ownerDoc->CSSLoader(),
1776 StyleCssRuleType::Style);
1777 if (!decl) {
1778 return false;
1780 decl->SetHTMLCSSStyleSheet(sheet);
1781 SetTo(decl.forget(), &aString);
1783 if (cachingAllowed) {
1784 MiscContainer* cont = GetMiscContainer();
1785 cont->Cache();
1788 return true;
1791 void nsAttrValue::SetMiscAtomOrString(const nsAString* aValue) {
1792 NS_ASSERTION(GetMiscContainer(), "Must have MiscContainer!");
1793 NS_ASSERTION(!GetMiscContainer()->mStringBits || IsInServoTraversal(),
1794 "Trying to re-set atom or string!");
1795 if (aValue) {
1796 uint32_t len = aValue->Length();
1797 // * We're allowing eCSSDeclaration attributes to store empty
1798 // strings as it can be beneficial to store an empty style
1799 // attribute as a parsed rule.
1800 // * We're allowing enumerated values because sometimes the empty
1801 // string corresponds to a particular enumerated value, especially
1802 // for enumerated values that are not limited enumerated.
1803 // Add other types as needed.
1804 NS_ASSERTION(len || Type() == eCSSDeclaration || Type() == eEnum,
1805 "Empty string?");
1806 MiscContainer* cont = GetMiscContainer();
1808 if (len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) {
1809 nsAtom* atom = MOZ_LIKELY(!IsInServoTraversal())
1810 ? NS_AtomizeMainThread(*aValue).take()
1811 : NS_Atomize(*aValue).take();
1812 NS_ENSURE_TRUE_VOID(atom);
1813 uintptr_t bits = reinterpret_cast<uintptr_t>(atom) | eAtomBase;
1815 // In the common case we're not in the servo traversal, and we can just
1816 // set the bits normally. The parallel case requires more care.
1817 if (MOZ_LIKELY(!IsInServoTraversal())) {
1818 cont->SetStringBitsMainThread(bits);
1819 } else if (!cont->mStringBits.compareExchange(0, bits)) {
1820 // We raced with somebody else setting the bits. Release our copy.
1821 atom->Release();
1823 } else {
1824 nsStringBuffer* buffer = GetStringBuffer(*aValue).take();
1825 NS_ENSURE_TRUE_VOID(buffer);
1826 uintptr_t bits = reinterpret_cast<uintptr_t>(buffer) | eStringBase;
1828 // In the common case we're not in the servo traversal, and we can just
1829 // set the bits normally. The parallel case requires more care.
1830 if (MOZ_LIKELY(!IsInServoTraversal())) {
1831 cont->SetStringBitsMainThread(bits);
1832 } else if (!cont->mStringBits.compareExchange(0, bits)) {
1833 // We raced with somebody else setting the bits. Release our copy.
1834 buffer->Release();
1840 void nsAttrValue::ResetMiscAtomOrString() {
1841 MiscContainer* cont = GetMiscContainer();
1842 void* ptr = MISC_STR_PTR(cont);
1843 if (ptr) {
1844 if (static_cast<ValueBaseType>(cont->mStringBits &
1845 NS_ATTRVALUE_BASETYPE_MASK) == eStringBase) {
1846 static_cast<nsStringBuffer*>(ptr)->Release();
1847 } else {
1848 static_cast<nsAtom*>(ptr)->Release();
1850 cont->SetStringBitsMainThread(0);
1854 void nsAttrValue::SetSVGType(ValueType aType, const void* aValue,
1855 const nsAString* aSerialized) {
1856 MOZ_ASSERT(IsSVGType(aType), "Not an SVG type");
1858 MiscContainer* cont = EnsureEmptyMiscContainer();
1859 // All SVG types are just pointers to classes so just setting any of them
1860 // will do. We'll lose type-safety but the signature of the calling
1861 // function should ensure we don't get anything unexpected, and once we
1862 // stick aValue in a union we lose type information anyway.
1863 cont->mValue.mSVGLength = static_cast<const SVGAnimatedLength*>(aValue);
1864 cont->mType = aType;
1865 SetMiscAtomOrString(aSerialized);
1868 MiscContainer* nsAttrValue::ClearMiscContainer() {
1869 MiscContainer* cont = nullptr;
1870 if (BaseType() == eOtherBase) {
1871 cont = GetMiscContainer();
1872 if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) {
1873 // This MiscContainer is shared, we need a new one.
1874 NS_RELEASE(cont);
1876 cont = AllocMiscContainer();
1877 SetPtrValueAndType(cont, eOtherBase);
1878 } else {
1879 switch (cont->mType) {
1880 case eCSSDeclaration: {
1881 MOZ_ASSERT(cont->mValue.mRefCount == 1);
1882 cont->Release();
1883 cont->Evict();
1884 NS_RELEASE(cont->mValue.mCSSDeclaration);
1885 break;
1887 case eShadowParts: {
1888 MOZ_ASSERT(cont->mValue.mRefCount == 1);
1889 cont->Release();
1890 delete cont->mValue.mShadowParts;
1891 break;
1893 case eURL: {
1894 NS_RELEASE(cont->mValue.mURL);
1895 break;
1897 case eAtomArray: {
1898 delete cont->mValue.mAtomArray;
1899 break;
1901 case eIntMarginValue: {
1902 delete cont->mValue.mIntMargin;
1903 break;
1905 default: {
1906 break;
1910 ResetMiscAtomOrString();
1911 } else {
1912 ResetIfSet();
1915 return cont;
1918 MiscContainer* nsAttrValue::EnsureEmptyMiscContainer() {
1919 MiscContainer* cont = ClearMiscContainer();
1920 if (cont) {
1921 MOZ_ASSERT(BaseType() == eOtherBase);
1922 ResetMiscAtomOrString();
1923 cont = GetMiscContainer();
1924 } else {
1925 cont = AllocMiscContainer();
1926 SetPtrValueAndType(cont, eOtherBase);
1929 return cont;
1932 bool nsAttrValue::EnsureEmptyAtomArray() {
1933 if (Type() == eAtomArray) {
1934 ResetMiscAtomOrString();
1935 GetAtomArrayValue()->Clear();
1936 return true;
1939 MiscContainer* cont = EnsureEmptyMiscContainer();
1940 cont->mValue.mAtomArray = new AttrAtomArray;
1941 cont->mType = eAtomArray;
1943 return true;
1946 already_AddRefed<nsStringBuffer> nsAttrValue::GetStringBuffer(
1947 const nsAString& aValue) const {
1948 uint32_t len = aValue.Length();
1949 if (!len) {
1950 return nullptr;
1953 RefPtr<nsStringBuffer> buf = nsStringBuffer::FromString(aValue);
1954 if (buf && (buf->StorageSize() / sizeof(char16_t) - 1) == len) {
1955 return buf.forget();
1958 buf = nsStringBuffer::Alloc((len + 1) * sizeof(char16_t));
1959 if (!buf) {
1960 return nullptr;
1962 char16_t* data = static_cast<char16_t*>(buf->Data());
1963 CopyUnicodeTo(aValue, 0, data, len);
1964 data[len] = char16_t(0);
1965 return buf.forget();
1968 size_t nsAttrValue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
1969 size_t n = 0;
1971 switch (BaseType()) {
1972 case eStringBase: {
1973 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
1974 n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0;
1975 break;
1977 case eOtherBase: {
1978 MiscContainer* container = GetMiscContainer();
1979 if (!container) {
1980 break;
1982 if (container->IsRefCounted() && container->mValue.mRefCount > 1) {
1983 // We don't report this MiscContainer at all in order to avoid
1984 // twice-reporting it.
1985 // TODO DMD, bug 1027551 - figure out how to report this ref-counted
1986 // object just once.
1987 break;
1989 n += aMallocSizeOf(container);
1991 void* otherPtr = MISC_STR_PTR(container);
1992 // We only count the size of the object pointed by otherPtr if it's a
1993 // string. When it's an atom, it's counted separatly.
1994 if (otherPtr && static_cast<ValueBaseType>(container->mStringBits &
1995 NS_ATTRVALUE_BASETYPE_MASK) ==
1996 eStringBase) {
1997 nsStringBuffer* str = static_cast<nsStringBuffer*>(otherPtr);
1998 n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0;
2001 if (Type() == eCSSDeclaration && container->mValue.mCSSDeclaration) {
2002 // TODO: mCSSDeclaration might be owned by another object which
2003 // would make us count them twice, bug 677493.
2004 // Bug 1281964: For DeclarationBlock if we do measure we'll
2005 // need a way to call the Servo heap_size_of function.
2006 // n += container->mCSSDeclaration->SizeOfIncludingThis(aMallocSizeOf);
2007 } else if (Type() == eAtomArray && container->mValue.mAtomArray) {
2008 // Don't measure each nsAtom, they are measured separatly.
2009 n += container->mValue.mAtomArray->mArray.ShallowSizeOfIncludingThis(
2010 aMallocSizeOf);
2012 break;
2014 case eAtomBase: // Atoms are counted separately.
2015 case eIntegerBase: // The value is in mBits, nothing to do.
2016 break;
2019 return n;