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/. */
8 * A struct that represents the value (type and actual data) of an
12 #include "mozilla/ArrayUtils.h"
13 #include "mozilla/DebugOnly.h"
14 #include "mozilla/HashFunctions.h"
16 #include "nsAttrValue.h"
17 #include "nsAttrValueInlines.h"
18 #include "nsUnicharUtils.h"
19 #include "mozilla/AttributeStyles.h"
20 #include "mozilla/ClearOnShutdown.h"
21 #include "mozilla/BloomFilter.h"
22 #include "mozilla/DeclarationBlock.h"
23 #include "mozilla/MemoryReporting.h"
24 #include "mozilla/ServoBindingTypes.h"
25 #include "mozilla/ServoUtils.h"
26 #include "mozilla/ShadowParts.h"
27 #include "mozilla/SVGAttrValueWrapper.h"
28 #include "mozilla/URLExtraData.h"
29 #include "mozilla/dom/Document.h"
30 #include "nsContentUtils.h"
31 #include "nsReadableUtils.h"
32 #include "nsStyledElement.h"
34 #include "ReferrerInfo.h"
37 using namespace mozilla
;
39 constexpr uint32_t kMiscContainerCacheSize
= 128;
40 static void* gMiscContainerCache
[kMiscContainerCacheSize
];
41 static uint32_t gMiscContainerCount
= 0;
44 * Global cache for eAtomArray MiscContainer objects, to speed up the parsing
45 * of class attributes with multiple class names.
46 * This cache doesn't keep anything alive - a MiscContainer removes itself from
47 * the cache once its last reference is dropped.
49 struct AtomArrayCache
{
50 // We don't keep any strong references, neither to the atom nor to the
51 // MiscContainer. The MiscContainer removes itself from the cache when
52 // the last reference to it is dropped, and the atom is kept alive by
54 using MapType
= nsTHashMap
<nsAtom
*, MiscContainer
*>;
56 static MiscContainer
* Lookup(nsAtom
* aValue
) {
57 if (auto* instance
= GetInstance()) {
58 return instance
->LookupImpl(aValue
);
63 static void Insert(nsAtom
* aValue
, MiscContainer
* aCont
) {
64 if (auto* instance
= GetInstance()) {
65 instance
->InsertImpl(aValue
, aCont
);
69 static void Remove(nsAtom
* aValue
) {
70 if (auto* instance
= GetInstance()) {
71 instance
->RemoveImpl(aValue
);
75 static AtomArrayCache
* GetInstance() {
76 static StaticAutoPtr
<AtomArrayCache
> sInstance
;
77 if (!sInstance
&& !PastShutdownPhase(ShutdownPhase::XPCOMShutdownFinal
)) {
78 sInstance
= new AtomArrayCache();
79 ClearOnShutdown(&sInstance
, ShutdownPhase::XPCOMShutdownFinal
);
85 MiscContainer
* LookupImpl(nsAtom
* aValue
) {
86 auto lookupResult
= mMap
.Lookup(aValue
);
87 return lookupResult
? *lookupResult
: nullptr;
90 void InsertImpl(nsAtom
* aValue
, MiscContainer
* aCont
) {
92 mMap
.InsertOrUpdate(aValue
, aCont
);
95 void RemoveImpl(nsAtom
* aValue
) { mMap
.Remove(aValue
); }
101 MiscContainer
* nsAttrValue::AllocMiscContainer() {
102 MOZ_ASSERT(NS_IsMainThread());
104 static_assert(sizeof(gMiscContainerCache
) <= 1024);
105 static_assert(sizeof(MiscContainer
) <= 32);
107 // Allocate MiscContainer objects in batches to improve performance.
108 if (gMiscContainerCount
== 0) {
109 for (; gMiscContainerCount
< kMiscContainerCacheSize
;
110 ++gMiscContainerCount
) {
111 gMiscContainerCache
[gMiscContainerCount
] =
112 moz_xmalloc(sizeof(MiscContainer
));
116 return new (gMiscContainerCache
[--gMiscContainerCount
]) MiscContainer();
120 void nsAttrValue::DeallocMiscContainer(MiscContainer
* aCont
) {
121 MOZ_ASSERT(NS_IsMainThread());
126 aCont
->~MiscContainer();
128 if (gMiscContainerCount
< kMiscContainerCacheSize
) {
129 gMiscContainerCache
[gMiscContainerCount
++] = aCont
;
136 bool MiscContainer::GetString(nsAString
& aString
) const {
138 void* ptr
= GetStringOrAtomPtr(isString
);
143 auto* buffer
= static_cast<mozilla::StringBuffer
*>(ptr
);
144 aString
.Assign(buffer
, buffer
->StorageSize() / sizeof(char16_t
) - 1);
146 static_cast<nsAtom
*>(ptr
)->ToString(aString
);
151 void MiscContainer::Cache() {
153 case nsAttrValue::eCSSDeclaration
: {
154 MOZ_ASSERT(IsRefCounted());
155 MOZ_ASSERT(mValue
.mRefCount
> 0);
156 MOZ_ASSERT(!mValue
.mCached
);
158 AttributeStyles
* attrStyles
=
159 mValue
.mCSSDeclaration
->GetAttributeStyles();
165 bool gotString
= GetString(str
);
170 attrStyles
->CacheStyleAttr(str
, this);
173 // This has to be immutable once it goes into the cache.
174 mValue
.mCSSDeclaration
->SetImmutable();
177 case nsAttrValue::eAtomArray
: {
178 MOZ_ASSERT(IsRefCounted());
179 MOZ_ASSERT(mValue
.mRefCount
> 0);
180 MOZ_ASSERT(!mValue
.mCached
);
182 nsAtom
* atom
= GetStoredAtom();
187 AtomArrayCache::Insert(atom
, this);
192 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
197 void MiscContainer::Evict() {
199 case nsAttrValue::eCSSDeclaration
: {
200 MOZ_ASSERT(IsRefCounted());
201 MOZ_ASSERT(mValue
.mRefCount
== 0);
203 if (!mValue
.mCached
) {
207 AttributeStyles
* attrStyles
=
208 mValue
.mCSSDeclaration
->GetAttributeStyles();
209 MOZ_ASSERT(attrStyles
);
212 DebugOnly
<bool> gotString
= GetString(str
);
213 MOZ_ASSERT(gotString
);
215 attrStyles
->EvictStyleAttr(str
, this);
219 case nsAttrValue::eAtomArray
: {
220 MOZ_ASSERT(IsRefCounted());
221 MOZ_ASSERT(mValue
.mRefCount
== 0);
223 if (!mValue
.mCached
) {
227 nsAtom
* atom
= GetStoredAtom();
230 AtomArrayCache::Remove(atom
);
237 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
242 nsTArray
<const nsAttrValue::EnumTable
*>* nsAttrValue::sEnumTableArray
= nullptr;
244 nsAttrValue::nsAttrValue() : mBits(0) {}
246 nsAttrValue::nsAttrValue(const nsAttrValue
& aOther
) : mBits(0) {
250 nsAttrValue::nsAttrValue(const nsAString
& aValue
) : mBits(0) { SetTo(aValue
); }
252 nsAttrValue::nsAttrValue(nsAtom
* aValue
) : mBits(0) { SetTo(aValue
); }
254 nsAttrValue::nsAttrValue(already_AddRefed
<DeclarationBlock
> aValue
,
255 const nsAString
* aSerialized
)
257 SetTo(std::move(aValue
), aSerialized
);
260 nsAttrValue::~nsAttrValue() { ResetIfSet(); }
263 void nsAttrValue::Init() {
264 MOZ_ASSERT(!sEnumTableArray
, "nsAttrValue already initialized");
265 sEnumTableArray
= new nsTArray
<const EnumTable
*>;
269 void nsAttrValue::Shutdown() {
270 MOZ_ASSERT(NS_IsMainThread());
271 delete sEnumTableArray
;
272 sEnumTableArray
= nullptr;
274 for (uint32_t i
= 0; i
< gMiscContainerCount
; ++i
) {
275 free(gMiscContainerCache
[i
]);
277 gMiscContainerCount
= 0;
280 void nsAttrValue::Reset() {
281 switch (BaseType()) {
283 if (auto* str
= static_cast<mozilla::StringBuffer
*>(GetPtr())) {
289 MiscContainer
* cont
= GetMiscContainer();
290 if (cont
->IsRefCounted() && cont
->mValue
.mRefCount
> 1) {
295 DeallocMiscContainer(ClearMiscContainer());
300 nsAtom
* atom
= GetAtomValue();
313 void nsAttrValue::SetTo(const nsAttrValue
& aOther
) {
314 if (this == &aOther
) {
318 switch (aOther
.BaseType()) {
321 if (auto* str
= static_cast<mozilla::StringBuffer
*>(aOther
.GetPtr())) {
323 SetPtrValueAndType(str
, eStringBase
);
332 nsAtom
* atom
= aOther
.GetAtomValue();
334 SetPtrValueAndType(atom
, eAtomBase
);
339 mBits
= aOther
.mBits
;
344 MiscContainer
* otherCont
= aOther
.GetMiscContainer();
345 if (otherCont
->IsRefCounted()) {
346 DeallocMiscContainer(ClearMiscContainer());
347 NS_ADDREF(otherCont
);
348 SetPtrValueAndType(otherCont
, eOtherBase
);
352 MiscContainer
* cont
= EnsureEmptyMiscContainer();
353 switch (otherCont
->mType
) {
355 cont
->mValue
.mInteger
= otherCont
->mValue
.mInteger
;
359 cont
->mValue
.mEnumValue
= otherCont
->mValue
.mEnumValue
;
363 cont
->mDoubleValue
= otherCont
->mDoubleValue
;
367 cont
->mValue
.mColor
= otherCont
->mValue
.mColor
;
372 case eCSSDeclaration
: {
373 MOZ_CRASH("These should be refcounted!");
376 NS_ADDREF(cont
->mValue
.mURL
= otherCont
->mValue
.mURL
);
380 cont
->mDoubleValue
= otherCont
->mDoubleValue
;
384 if (IsSVGType(otherCont
->mType
)) {
385 // All SVG types are just pointers to classes and will therefore have
386 // the same size so it doesn't really matter which one we assign
387 cont
->mValue
.mSVGLength
= otherCont
->mValue
.mSVGLength
;
389 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
396 if (void* otherPtr
= otherCont
->GetStringOrAtomPtr(isString
)) {
398 static_cast<mozilla::StringBuffer
*>(otherPtr
)->AddRef();
400 static_cast<nsAtom
*>(otherPtr
)->AddRef();
402 cont
->SetStringBitsMainThread(otherCont
->mStringBits
);
404 // Note, set mType after switch-case, otherwise EnsureEmptyAtomArray doesn't
406 cont
->mType
= otherCont
->mType
;
409 void nsAttrValue::SetTo(const nsAString
& aValue
) {
411 mozilla::StringBuffer
* buf
= GetStringBuffer(aValue
).take();
413 SetPtrValueAndType(buf
, eStringBase
);
417 void nsAttrValue::SetTo(nsAtom
* aValue
) {
421 SetPtrValueAndType(aValue
, eAtomBase
);
425 void nsAttrValue::SetTo(int16_t aInt
) {
427 SetIntValueAndType(aInt
, eInteger
, nullptr);
430 void nsAttrValue::SetTo(int32_t aInt
, const nsAString
* aSerialized
) {
432 SetIntValueAndType(aInt
, eInteger
, aSerialized
);
435 void nsAttrValue::SetTo(double aValue
, const nsAString
* aSerialized
) {
436 MiscContainer
* cont
= EnsureEmptyMiscContainer();
437 cont
->mDoubleValue
= aValue
;
438 cont
->mType
= eDoubleValue
;
439 SetMiscAtomOrString(aSerialized
);
442 void nsAttrValue::SetTo(already_AddRefed
<DeclarationBlock
> aValue
,
443 const nsAString
* aSerialized
) {
444 MiscContainer
* cont
= EnsureEmptyMiscContainer();
445 MOZ_ASSERT(cont
->mValue
.mRefCount
== 0);
446 cont
->mValue
.mCSSDeclaration
= aValue
.take();
447 cont
->mType
= eCSSDeclaration
;
449 SetMiscAtomOrString(aSerialized
);
450 MOZ_ASSERT(cont
->mValue
.mRefCount
== 1);
453 void nsAttrValue::SetTo(nsIURI
* aValue
, const nsAString
* aSerialized
) {
454 MiscContainer
* cont
= EnsureEmptyMiscContainer();
455 NS_ADDREF(cont
->mValue
.mURL
= aValue
);
457 SetMiscAtomOrString(aSerialized
);
460 void nsAttrValue::SetToSerialized(const nsAttrValue
& aOther
) {
461 if (aOther
.Type() != nsAttrValue::eString
&&
462 aOther
.Type() != nsAttrValue::eAtom
) {
464 aOther
.ToString(val
);
471 void nsAttrValue::SetTo(const SVGAnimatedOrient
& aValue
,
472 const nsAString
* aSerialized
) {
473 SetSVGType(eSVGOrient
, &aValue
, aSerialized
);
476 void nsAttrValue::SetTo(const SVGAnimatedIntegerPair
& aValue
,
477 const nsAString
* aSerialized
) {
478 SetSVGType(eSVGIntegerPair
, &aValue
, aSerialized
);
481 void nsAttrValue::SetTo(const SVGAnimatedLength
& aValue
,
482 const nsAString
* aSerialized
) {
483 SetSVGType(eSVGLength
, &aValue
, aSerialized
);
486 void nsAttrValue::SetTo(const SVGLengthList
& aValue
,
487 const nsAString
* aSerialized
) {
488 // While an empty string will parse as a length list, there's no need to store
489 // it (and SetMiscAtomOrString will assert if we try)
490 if (aSerialized
&& aSerialized
->IsEmpty()) {
491 aSerialized
= nullptr;
493 SetSVGType(eSVGLengthList
, &aValue
, aSerialized
);
496 void nsAttrValue::SetTo(const SVGNumberList
& aValue
,
497 const nsAString
* aSerialized
) {
498 // While an empty string will parse as a number list, there's no need to store
499 // it (and SetMiscAtomOrString will assert if we try)
500 if (aSerialized
&& aSerialized
->IsEmpty()) {
501 aSerialized
= nullptr;
503 SetSVGType(eSVGNumberList
, &aValue
, aSerialized
);
506 void nsAttrValue::SetTo(const SVGAnimatedNumberPair
& aValue
,
507 const nsAString
* aSerialized
) {
508 SetSVGType(eSVGNumberPair
, &aValue
, aSerialized
);
511 void nsAttrValue::SetTo(const SVGPathData
& aValue
,
512 const nsAString
* aSerialized
) {
513 // While an empty string will parse as path data, there's no need to store it
514 // (and SetMiscAtomOrString will assert if we try)
515 if (aSerialized
&& aSerialized
->IsEmpty()) {
516 aSerialized
= nullptr;
518 SetSVGType(eSVGPathData
, &aValue
, aSerialized
);
521 void nsAttrValue::SetTo(const SVGPointList
& aValue
,
522 const nsAString
* aSerialized
) {
523 // While an empty string will parse as a point list, there's no need to store
524 // it (and SetMiscAtomOrString will assert if we try)
525 if (aSerialized
&& aSerialized
->IsEmpty()) {
526 aSerialized
= nullptr;
528 SetSVGType(eSVGPointList
, &aValue
, aSerialized
);
531 void nsAttrValue::SetTo(const SVGAnimatedPreserveAspectRatio
& aValue
,
532 const nsAString
* aSerialized
) {
533 SetSVGType(eSVGPreserveAspectRatio
, &aValue
, aSerialized
);
536 void nsAttrValue::SetTo(const SVGStringList
& aValue
,
537 const nsAString
* aSerialized
) {
538 // While an empty string will parse as a string list, there's no need to store
539 // it (and SetMiscAtomOrString will assert if we try)
540 if (aSerialized
&& aSerialized
->IsEmpty()) {
541 aSerialized
= nullptr;
543 SetSVGType(eSVGStringList
, &aValue
, aSerialized
);
546 void nsAttrValue::SetTo(const SVGTransformList
& aValue
,
547 const nsAString
* aSerialized
) {
548 // While an empty string will parse as a transform list, there's no need to
549 // store it (and SetMiscAtomOrString will assert if we try)
550 if (aSerialized
&& aSerialized
->IsEmpty()) {
551 aSerialized
= nullptr;
553 SetSVGType(eSVGTransformList
, &aValue
, aSerialized
);
556 void nsAttrValue::SetTo(const SVGAnimatedViewBox
& aValue
,
557 const nsAString
* aSerialized
) {
558 SetSVGType(eSVGViewBox
, &aValue
, aSerialized
);
561 void nsAttrValue::SwapValueWith(nsAttrValue
& aOther
) {
562 uintptr_t tmp
= aOther
.mBits
;
563 aOther
.mBits
= mBits
;
567 void nsAttrValue::RemoveDuplicatesFromAtomArray() {
568 if (Type() != eAtomArray
) {
572 const AttrAtomArray
* currentAtomArray
= GetMiscContainer()->mValue
.mAtomArray
;
573 UniquePtr
<AttrAtomArray
> deduplicatedAtomArray
=
574 currentAtomArray
->CreateDeduplicatedCopyIfDifferent();
576 if (!deduplicatedAtomArray
) {
577 // No duplicates found. Leave this value unchanged.
581 // We found duplicates. Wrap the new atom array into a fresh MiscContainer,
582 // and copy over the existing container's string or atom.
584 MiscContainer
* oldCont
= GetMiscContainer();
585 MOZ_ASSERT(oldCont
->IsRefCounted());
587 uintptr_t stringBits
= 0;
588 bool isString
= false;
589 if (void* otherPtr
= oldCont
->GetStringOrAtomPtr(isString
)) {
590 stringBits
= oldCont
->mStringBits
;
592 static_cast<mozilla::StringBuffer
*>(otherPtr
)->AddRef();
594 static_cast<nsAtom
*>(otherPtr
)->AddRef();
598 MiscContainer
* cont
= EnsureEmptyMiscContainer();
599 MOZ_ASSERT(cont
->mValue
.mRefCount
== 0);
600 cont
->mValue
.mAtomArray
= deduplicatedAtomArray
.release();
601 cont
->mType
= eAtomArray
;
603 MOZ_ASSERT(cont
->mValue
.mRefCount
== 1);
604 cont
->SetStringBitsMainThread(stringBits
);
606 // Don't cache the new container. It would stomp over the undeduplicated
607 // value in the cache. But we could have a separate cache for deduplicated
608 // atom arrays, if repeated deduplication shows up in profiles.
611 void nsAttrValue::ToString(nsAString
& aResult
) const {
612 MiscContainer
* cont
= nullptr;
613 if (BaseType() == eOtherBase
) {
614 cont
= GetMiscContainer();
616 if (cont
->GetString(aResult
)) {
623 if (auto* str
= static_cast<mozilla::StringBuffer
*>(GetPtr())) {
624 aResult
.Assign(str
, str
->StorageSize() / sizeof(char16_t
) - 1);
631 auto* atom
= static_cast<nsAtom
*>(GetPtr());
632 atom
->ToString(aResult
);
637 intStr
.AppendInt(GetIntegerValue());
644 MOZ_ASSERT_UNREACHABLE("color attribute without string data");
650 GetEnumString(aResult
, false);
656 str
.AppendFloat(cont
->mDoubleValue
);
658 str
.AppendInt(GetIntInternal());
660 aResult
= str
+ u
"%"_ns
;
664 case eCSSDeclaration
: {
666 MiscContainer
* container
= GetMiscContainer();
667 if (DeclarationBlock
* decl
= container
->mValue
.mCSSDeclaration
) {
668 nsAutoCString result
;
669 decl
->ToString(result
);
670 CopyUTF8toUTF16(result
, aResult
);
673 // This can be reached during parallel selector matching with attribute
674 // selectors on the style attribute. SetMiscAtomOrString handles this
675 // case, and as of this writing this is the only consumer that needs it.
676 const_cast<nsAttrValue
*>(this)->SetMiscAtomOrString(&aResult
);
682 aResult
.AppendFloat(GetDoubleValue());
685 case eSVGIntegerPair
: {
686 SVGAttrValueWrapper::ToString(
687 GetMiscContainer()->mValue
.mSVGAnimatedIntegerPair
, aResult
);
691 SVGAttrValueWrapper::ToString(
692 GetMiscContainer()->mValue
.mSVGAnimatedOrient
, aResult
);
696 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue
.mSVGLength
,
700 case eSVGLengthList
: {
701 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue
.mSVGLengthList
,
705 case eSVGNumberList
: {
706 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue
.mSVGNumberList
,
710 case eSVGNumberPair
: {
711 SVGAttrValueWrapper::ToString(
712 GetMiscContainer()->mValue
.mSVGAnimatedNumberPair
, aResult
);
716 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue
.mSVGPathData
,
720 case eSVGPointList
: {
721 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue
.mSVGPointList
,
725 case eSVGPreserveAspectRatio
: {
726 SVGAttrValueWrapper::ToString(
727 GetMiscContainer()->mValue
.mSVGAnimatedPreserveAspectRatio
, aResult
);
730 case eSVGStringList
: {
731 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue
.mSVGStringList
,
735 case eSVGTransformList
: {
736 SVGAttrValueWrapper::ToString(
737 GetMiscContainer()->mValue
.mSVGTransformList
, aResult
);
741 SVGAttrValueWrapper::ToString(
742 GetMiscContainer()->mValue
.mSVGAnimatedViewBox
, aResult
);
752 already_AddRefed
<nsAtom
> nsAttrValue::GetAsAtom() const {
755 return NS_AtomizeMainThread(GetStringValue());
758 RefPtr
<nsAtom
> atom
= GetAtomValue();
759 return atom
.forget();
765 return NS_AtomizeMainThread(val
);
770 const nsCheapString
nsAttrValue::GetStringValue() const {
771 MOZ_ASSERT(Type() == eString
, "wrong type");
773 return nsCheapString(static_cast<mozilla::StringBuffer
*>(GetPtr()));
776 bool nsAttrValue::GetColorValue(nscolor
& aColor
) const {
777 if (Type() != eColor
) {
778 // Unparseable value, treat as unset.
779 NS_ASSERTION(Type() == eString
, "unexpected type for color-valued attr");
783 aColor
= GetMiscContainer()->mValue
.mColor
;
787 void nsAttrValue::GetEnumString(nsAString
& aResult
, bool aRealTag
) const {
788 MOZ_ASSERT(Type() == eEnum
, "wrong type");
790 uint32_t allEnumBits
= (BaseType() == eIntegerBase
)
791 ? static_cast<uint32_t>(GetIntInternal())
792 : GetMiscContainer()->mValue
.mEnumValue
;
793 int16_t val
= allEnumBits
>> NS_ATTRVALUE_ENUMTABLEINDEX_BITS
;
794 const EnumTable
* table
= sEnumTableArray
->ElementAt(
795 allEnumBits
& NS_ATTRVALUE_ENUMTABLEINDEX_MASK
);
798 if (table
->value
== val
) {
799 aResult
.AssignASCII(table
->tag
);
801 allEnumBits
& NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER
) {
802 nsContentUtils::ASCIIToUpper(aResult
);
809 MOZ_ASSERT_UNREACHABLE("couldn't find value in EnumTable");
812 UniquePtr
<AttrAtomArray
> AttrAtomArray::CreateDeduplicatedCopyIfDifferentImpl()
814 MOZ_ASSERT(mMayContainDuplicates
);
816 bool usingHashTable
= false;
817 BitBloomFilter
<8, nsAtom
> filter
;
818 nsTHashSet
<nsAtom
*> hash
;
820 auto CheckDuplicate
= [&](size_t i
) {
821 nsAtom
* atom
= mArray
[i
];
822 if (!usingHashTable
) {
823 if (!filter
.mightContain(atom
)) {
827 for (size_t j
= 0; j
< i
; ++j
) {
828 hash
.Insert(mArray
[j
]);
830 usingHashTable
= true;
832 return !hash
.EnsureInserted(atom
);
835 size_t len
= mArray
.Length();
836 UniquePtr
<AttrAtomArray
> deduplicatedArray
;
837 for (size_t i
= 0; i
< len
; ++i
) {
838 if (!CheckDuplicate(i
)) {
839 if (deduplicatedArray
) {
840 deduplicatedArray
->mArray
.AppendElement(mArray
[i
]);
844 // We've found a duplicate!
845 if (!deduplicatedArray
) {
846 // Allocate the deduplicated copy and copy the preceding elements into it.
847 deduplicatedArray
= MakeUnique
<AttrAtomArray
>();
848 deduplicatedArray
->mMayContainDuplicates
= false;
849 deduplicatedArray
->mArray
.SetCapacity(len
- 1);
850 for (size_t indexToCopy
= 0; indexToCopy
< i
; indexToCopy
++) {
851 deduplicatedArray
->mArray
.AppendElement(mArray
[indexToCopy
]);
856 if (!deduplicatedArray
) {
857 // This AttrAtomArray doesn't contain any duplicates, cache this information
858 // for future invocations.
859 mMayContainDuplicates
= false;
861 return deduplicatedArray
;
864 uint32_t nsAttrValue::GetAtomCount() const {
865 ValueType type
= Type();
871 if (type
== eAtomArray
) {
872 return GetAtomArrayValue()->mArray
.Length();
878 nsAtom
* nsAttrValue::AtomAt(int32_t aIndex
) const {
879 MOZ_ASSERT(aIndex
>= 0, "Index must not be negative");
880 MOZ_ASSERT(GetAtomCount() > uint32_t(aIndex
), "aIndex out of range");
882 if (BaseType() == eAtomBase
) {
883 return GetAtomValue();
886 NS_ASSERTION(Type() == eAtomArray
, "GetAtomCount must be confused");
887 return GetAtomArrayValue()->mArray
.ElementAt(aIndex
);
890 uint32_t nsAttrValue::HashValue() const {
891 switch (BaseType()) {
893 if (auto* str
= static_cast<mozilla::StringBuffer
*>(GetPtr())) {
894 uint32_t len
= str
->StorageSize() / sizeof(char16_t
) - 1;
895 return HashString(static_cast<char16_t
*>(str
->Data()), len
);
905 // mBits and uint32_t might have different size. This should silence
906 // any warnings or compile-errors. This is what the implementation of
907 // NS_PTR_TO_INT32 does to take care of the same problem.
912 MiscContainer
* cont
= GetMiscContainer();
913 if (static_cast<ValueBaseType
>(cont
->mStringBits
&
914 NS_ATTRVALUE_BASETYPE_MASK
) == eAtomBase
) {
915 return cont
->mStringBits
- 0;
918 switch (cont
->mType
) {
920 return cont
->mValue
.mInteger
;
923 return cont
->mValue
.mEnumValue
;
926 return cont
->mDoubleValue
;
929 return cont
->mValue
.mColor
;
931 case eCSSDeclaration
: {
932 return NS_PTR_TO_INT32(cont
->mValue
.mCSSDeclaration
);
937 return HashString(str
);
941 for (const auto& atom
: cont
->mValue
.mAtomArray
->mArray
) {
942 hash
= AddToHash(hash
, atom
.get());
947 // XXX this is crappy, but oh well
948 return cont
->mDoubleValue
;
951 if (IsSVGType(cont
->mType
)) {
952 // All SVG types are just pointers to classes so we can treat them alike
953 return NS_PTR_TO_INT32(cont
->mValue
.mSVGLength
);
955 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
961 bool nsAttrValue::Equals(const nsAttrValue
& aOther
) const {
962 if (BaseType() != aOther
.BaseType()) {
966 switch (BaseType()) {
968 return GetStringValue().Equals(aOther
.GetStringValue());
975 return mBits
== aOther
.mBits
;
979 MiscContainer
* thisCont
= GetMiscContainer();
980 MiscContainer
* otherCont
= aOther
.GetMiscContainer();
981 if (thisCont
== otherCont
) {
985 if (thisCont
->mType
!= otherCont
->mType
) {
989 bool needsStringComparison
= false;
991 switch (thisCont
->mType
) {
993 if (thisCont
->mValue
.mInteger
== otherCont
->mValue
.mInteger
) {
994 needsStringComparison
= true;
999 if (thisCont
->mValue
.mEnumValue
== otherCont
->mValue
.mEnumValue
) {
1000 needsStringComparison
= true;
1005 if (thisCont
->mDoubleValue
== otherCont
->mDoubleValue
) {
1006 needsStringComparison
= true;
1011 if (thisCont
->mValue
.mColor
== otherCont
->mValue
.mColor
) {
1012 needsStringComparison
= true;
1016 case eCSSDeclaration
: {
1017 return thisCont
->mValue
.mCSSDeclaration
==
1018 otherCont
->mValue
.mCSSDeclaration
;
1021 return thisCont
->mValue
.mURL
== otherCont
->mValue
.mURL
;
1024 // For classlists we could be insensitive to order, however
1025 // classlists are never mapped attributes so they are never compared.
1027 if (!(*thisCont
->mValue
.mAtomArray
== *otherCont
->mValue
.mAtomArray
)) {
1031 needsStringComparison
= true;
1034 case eDoubleValue
: {
1035 return thisCont
->mDoubleValue
== otherCont
->mDoubleValue
;
1038 if (IsSVGType(thisCont
->mType
)) {
1039 // Currently this method is never called for nsAttrValue objects that
1040 // point to SVG data types.
1041 // If that changes then we probably want to add methods to the
1042 // corresponding SVG types to compare their base values.
1043 // As a shortcut, however, we can begin by comparing the pointers.
1044 MOZ_ASSERT(false, "Comparing nsAttrValues that point to SVG data");
1047 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
1051 if (needsStringComparison
) {
1052 if (thisCont
->mStringBits
== otherCont
->mStringBits
) {
1055 if ((static_cast<ValueBaseType
>(thisCont
->mStringBits
&
1056 NS_ATTRVALUE_BASETYPE_MASK
) ==
1058 (static_cast<ValueBaseType
>(otherCont
->mStringBits
&
1059 NS_ATTRVALUE_BASETYPE_MASK
) ==
1061 return nsCheapString(reinterpret_cast<mozilla::StringBuffer
*>(
1062 static_cast<uintptr_t>(thisCont
->mStringBits
)))
1063 .Equals(nsCheapString(reinterpret_cast<mozilla::StringBuffer
*>(
1064 static_cast<uintptr_t>(otherCont
->mStringBits
))));
1070 bool nsAttrValue::Equals(const nsAString
& aValue
,
1071 nsCaseTreatment aCaseSensitive
) const {
1072 switch (BaseType()) {
1074 if (auto* str
= static_cast<mozilla::StringBuffer
*>(GetPtr())) {
1075 nsDependentString
dep(static_cast<char16_t
*>(str
->Data()),
1076 str
->StorageSize() / sizeof(char16_t
) - 1);
1077 return aCaseSensitive
== eCaseMatters
1078 ? aValue
.Equals(dep
)
1079 : nsContentUtils::EqualsIgnoreASCIICase(aValue
, dep
);
1081 return aValue
.IsEmpty();
1084 auto* atom
= static_cast<nsAtom
*>(GetPtr());
1085 if (aCaseSensitive
== eCaseMatters
) {
1086 return atom
->Equals(aValue
);
1088 return nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(atom
),
1097 return aCaseSensitive
== eCaseMatters
1098 ? val
.Equals(aValue
)
1099 : nsContentUtils::EqualsIgnoreASCIICase(val
, aValue
);
1102 bool nsAttrValue::Equals(const nsAtom
* aValue
,
1103 nsCaseTreatment aCaseSensitive
) const {
1104 switch (BaseType()) {
1106 auto* atom
= static_cast<nsAtom
*>(GetPtr());
1107 if (atom
== aValue
) {
1110 if (aCaseSensitive
== eCaseMatters
) {
1113 if (atom
->IsAsciiLowercase() && aValue
->IsAsciiLowercase()) {
1116 return nsContentUtils::EqualsIgnoreASCIICase(
1117 nsDependentAtomString(atom
), nsDependentAtomString(aValue
));
1120 if (auto* str
= static_cast<mozilla::StringBuffer
*>(GetPtr())) {
1121 size_t strLen
= str
->StorageSize() / sizeof(char16_t
) - 1;
1122 if (aValue
->GetLength() != strLen
) {
1125 const char16_t
* strData
= static_cast<char16_t
*>(str
->Data());
1126 const char16_t
* valData
= aValue
->GetUTF16String();
1127 if (aCaseSensitive
== eCaseMatters
) {
1128 // Avoid string construction / destruction for the easy case.
1129 return ArrayEqual(strData
, valData
, strLen
);
1131 nsDependentSubstring
depStr(strData
, strLen
);
1132 nsDependentSubstring
depVal(valData
, strLen
);
1133 return nsContentUtils::EqualsIgnoreASCIICase(depStr
, depVal
);
1135 return aValue
->IsEmpty();
1143 nsDependentAtomString
dep(aValue
);
1144 return aCaseSensitive
== eCaseMatters
1146 : nsContentUtils::EqualsIgnoreASCIICase(val
, dep
);
1149 struct HasPrefixFn
{
1150 static bool Check(const char16_t
* aAttrValue
, size_t aAttrLen
,
1151 const nsAString
& aSearchValue
,
1152 nsCaseTreatment aCaseSensitive
) {
1153 if (aCaseSensitive
== eCaseMatters
) {
1154 if (aSearchValue
.Length() > aAttrLen
) {
1157 return !memcmp(aAttrValue
, aSearchValue
.BeginReading(),
1158 aSearchValue
.Length() * sizeof(char16_t
));
1160 return StringBeginsWith(nsDependentString(aAttrValue
, aAttrLen
),
1162 nsASCIICaseInsensitiveStringComparator
);
1166 struct HasSuffixFn
{
1167 static bool Check(const char16_t
* aAttrValue
, size_t aAttrLen
,
1168 const nsAString
& aSearchValue
,
1169 nsCaseTreatment aCaseSensitive
) {
1170 if (aCaseSensitive
== eCaseMatters
) {
1171 if (aSearchValue
.Length() > aAttrLen
) {
1174 return !memcmp(aAttrValue
+ aAttrLen
- aSearchValue
.Length(),
1175 aSearchValue
.BeginReading(),
1176 aSearchValue
.Length() * sizeof(char16_t
));
1178 return StringEndsWith(nsDependentString(aAttrValue
, aAttrLen
), aSearchValue
,
1179 nsASCIICaseInsensitiveStringComparator
);
1183 struct HasSubstringFn
{
1184 static bool Check(const char16_t
* aAttrValue
, size_t aAttrLen
,
1185 const nsAString
& aSearchValue
,
1186 nsCaseTreatment aCaseSensitive
) {
1187 if (aCaseSensitive
== eCaseMatters
) {
1188 if (aSearchValue
.IsEmpty()) {
1191 const char16_t
* end
= aAttrValue
+ aAttrLen
;
1192 return std::search(aAttrValue
, end
, aSearchValue
.BeginReading(),
1193 aSearchValue
.EndReading()) != end
;
1195 return FindInReadable(aSearchValue
, nsDependentString(aAttrValue
, aAttrLen
),
1196 nsASCIICaseInsensitiveStringComparator
);
1200 template <typename F
>
1201 bool nsAttrValue::SubstringCheck(const nsAString
& aValue
,
1202 nsCaseTreatment aCaseSensitive
) const {
1203 switch (BaseType()) {
1205 if (auto* str
= static_cast<mozilla::StringBuffer
*>(GetPtr())) {
1206 return F::Check(static_cast<char16_t
*>(str
->Data()),
1207 str
->StorageSize() / sizeof(char16_t
) - 1, aValue
,
1210 return aValue
.IsEmpty();
1213 auto* atom
= static_cast<nsAtom
*>(GetPtr());
1214 return F::Check(atom
->GetUTF16String(), atom
->GetLength(), aValue
,
1223 return F::Check(val
.BeginReading(), val
.Length(), aValue
, aCaseSensitive
);
1226 bool nsAttrValue::HasPrefix(const nsAString
& aValue
,
1227 nsCaseTreatment aCaseSensitive
) const {
1228 return SubstringCheck
<HasPrefixFn
>(aValue
, aCaseSensitive
);
1231 bool nsAttrValue::HasSuffix(const nsAString
& aValue
,
1232 nsCaseTreatment aCaseSensitive
) const {
1233 return SubstringCheck
<HasSuffixFn
>(aValue
, aCaseSensitive
);
1236 bool nsAttrValue::HasSubstring(const nsAString
& aValue
,
1237 nsCaseTreatment aCaseSensitive
) const {
1238 return SubstringCheck
<HasSubstringFn
>(aValue
, aCaseSensitive
);
1241 bool nsAttrValue::EqualsAsStrings(const nsAttrValue
& aOther
) const {
1242 if (Type() == aOther
.Type()) {
1243 return Equals(aOther
);
1246 // We need to serialize at least one nsAttrValue before passing to
1247 // Equals(const nsAString&), but we can avoid unnecessarily serializing both
1248 // by checking if one is already of a string type.
1249 bool thisIsString
= (BaseType() == eStringBase
|| BaseType() == eAtomBase
);
1250 const nsAttrValue
& lhs
= thisIsString
? *this : aOther
;
1251 const nsAttrValue
& rhs
= thisIsString
? aOther
: *this;
1253 switch (rhs
.BaseType()) {
1255 return lhs
.Equals(rhs
.GetAtomValue(), eCaseMatters
);
1258 return lhs
.Equals(rhs
.GetStringValue(), eCaseMatters
);
1263 return lhs
.Equals(val
, eCaseMatters
);
1268 bool nsAttrValue::Contains(nsAtom
* aValue
,
1269 nsCaseTreatment aCaseSensitive
) const {
1270 switch (BaseType()) {
1272 nsAtom
* atom
= GetAtomValue();
1273 if (aCaseSensitive
== eCaseMatters
) {
1274 return aValue
== atom
;
1277 // For performance reasons, don't do a full on unicode case insensitive
1278 // string comparison. This is only used for quirks mode anyway.
1279 return nsContentUtils::EqualsIgnoreASCIICase(aValue
, atom
);
1282 if (Type() == eAtomArray
) {
1283 const AttrAtomArray
* array
= GetAtomArrayValue();
1284 if (aCaseSensitive
== eCaseMatters
) {
1285 return array
->mArray
.Contains(aValue
);
1288 for (const RefPtr
<nsAtom
>& cur
: array
->mArray
) {
1289 // For performance reasons, don't do a full on unicode case
1290 // insensitive string comparison. This is only used for quirks mode
1292 if (nsContentUtils::EqualsIgnoreASCIICase(aValue
, cur
)) {
1303 struct AtomArrayStringComparator
{
1304 bool Equals(nsAtom
* atom
, const nsAString
& string
) const {
1305 return atom
->Equals(string
);
1309 bool nsAttrValue::Contains(const nsAString
& aValue
) const {
1310 switch (BaseType()) {
1312 nsAtom
* atom
= GetAtomValue();
1313 return atom
->Equals(aValue
);
1316 if (Type() == eAtomArray
) {
1317 const AttrAtomArray
* array
= GetAtomArrayValue();
1318 return array
->mArray
.Contains(aValue
, AtomArrayStringComparator());
1326 void nsAttrValue::ParseAtom(const nsAString
& aValue
) {
1329 RefPtr
<nsAtom
> atom
= NS_Atomize(aValue
);
1331 SetPtrValueAndType(atom
.forget().take(), eAtomBase
);
1335 void nsAttrValue::ParseAtomArray(nsAtom
* aValue
) {
1336 if (MiscContainer
* cont
= AtomArrayCache::Lookup(aValue
)) {
1337 // Set our MiscContainer to the cached one.
1339 SetPtrValueAndType(cont
, eOtherBase
);
1343 const char16_t
* iter
= aValue
->GetUTF16String();
1344 const char16_t
* end
= iter
+ aValue
->GetLength();
1345 bool hasSpace
= false;
1347 // skip initial whitespace
1348 while (iter
!= end
&& nsContentUtils::IsHTMLWhitespace(*iter
)) {
1354 // The value is empty or only contains whitespace.
1355 // Set this attribute to the string value.
1356 // We don't call the SetTo(nsAtom*) overload because doing so would
1357 // leave us with a classList of length 1.
1358 SetTo(nsDependentAtomString(aValue
));
1362 const char16_t
* start
= iter
;
1364 // get first - and often only - atom
1367 } while (iter
!= end
&& !nsContentUtils::IsHTMLWhitespace(*iter
));
1369 RefPtr
<nsAtom
> classAtom
= iter
== end
&& !hasSpace
1370 ? RefPtr
<nsAtom
>(aValue
).forget()
1371 : NS_AtomizeMainThread(Substring(start
, iter
));
1378 while (iter
!= end
&& nsContentUtils::IsHTMLWhitespace(*iter
)) {
1383 if (iter
== end
&& !hasSpace
) {
1384 // we only found one classname and there was no whitespace so
1385 // don't bother storing a list
1387 nsAtom
* atom
= nullptr;
1388 classAtom
.swap(atom
);
1389 SetPtrValueAndType(atom
, eAtomBase
);
1393 // We have at least one class atom. Create a new AttrAtomArray.
1394 AttrAtomArray
* array
= new AttrAtomArray
;
1396 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1397 // pretended earlier.
1398 array
->mArray
.AppendElement(std::move(classAtom
));
1400 // parse the rest of the classnames
1401 while (iter
!= end
) {
1406 } while (iter
!= end
&& !nsContentUtils::IsHTMLWhitespace(*iter
));
1408 classAtom
= NS_AtomizeMainThread(Substring(start
, iter
));
1410 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1411 // pretended earlier.
1412 array
->mArray
.AppendElement(std::move(classAtom
));
1413 array
->mMayContainDuplicates
= true;
1416 while (iter
!= end
&& nsContentUtils::IsHTMLWhitespace(*iter
)) {
1421 // Wrap the AtomArray into a fresh MiscContainer.
1422 MiscContainer
* cont
= EnsureEmptyMiscContainer();
1423 MOZ_ASSERT(cont
->mValue
.mRefCount
== 0);
1424 cont
->mValue
.mAtomArray
= array
;
1425 cont
->mType
= eAtomArray
;
1427 MOZ_ASSERT(cont
->mValue
.mRefCount
== 1);
1429 // Assign the atom to the container's string bits (like SetMiscAtomOrString
1431 MOZ_ASSERT(!IsInServoTraversal());
1433 uintptr_t bits
= reinterpret_cast<uintptr_t>(aValue
) | eAtomBase
;
1434 cont
->SetStringBitsMainThread(bits
);
1436 // Put the container in the cache.
1440 void nsAttrValue::ParseAtomArray(const nsAString
& aValue
) {
1441 if (aValue
.IsVoid()) {
1444 RefPtr
<nsAtom
> atom
= NS_AtomizeMainThread(aValue
);
1445 ParseAtomArray(atom
);
1449 void nsAttrValue::ParseStringOrAtom(const nsAString
& aValue
) {
1450 uint32_t len
= aValue
.Length();
1451 // Don't bother with atoms if it's an empty string since
1452 // we can store those efficiently anyway.
1453 if (len
&& len
<= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM
) {
1460 void nsAttrValue::ParsePartMapping(const nsAString
& aValue
) {
1462 MiscContainer
* cont
= EnsureEmptyMiscContainer();
1464 cont
->mType
= eShadowParts
;
1465 cont
->mValue
.mShadowParts
= new ShadowParts(ShadowParts::Parse(aValue
));
1467 SetMiscAtomOrString(&aValue
);
1468 MOZ_ASSERT(cont
->mValue
.mRefCount
== 1);
1471 void nsAttrValue::SetIntValueAndType(int32_t aValue
, ValueType aType
,
1472 const nsAString
* aStringValue
) {
1473 if (aStringValue
|| aValue
> NS_ATTRVALUE_INTEGERTYPE_MAXVALUE
||
1474 aValue
< NS_ATTRVALUE_INTEGERTYPE_MINVALUE
) {
1475 MiscContainer
* cont
= EnsureEmptyMiscContainer();
1478 cont
->mValue
.mInteger
= aValue
;
1482 cont
->mDoubleValue
= aValue
;
1486 cont
->mValue
.mEnumValue
= aValue
;
1490 MOZ_ASSERT_UNREACHABLE("unknown integer type");
1494 cont
->mType
= aType
;
1495 SetMiscAtomOrString(aStringValue
);
1497 NS_ASSERTION(!mBits
, "Reset before calling SetIntValueAndType!");
1498 mBits
= (aValue
* NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER
) | aType
;
1502 void nsAttrValue::SetDoubleValueAndType(double aValue
, ValueType aType
,
1503 const nsAString
* aStringValue
) {
1504 MOZ_ASSERT(aType
== eDoubleValue
|| aType
== ePercent
, "Unexpected type");
1505 MiscContainer
* cont
= EnsureEmptyMiscContainer();
1506 cont
->mDoubleValue
= aValue
;
1507 cont
->mType
= aType
;
1508 SetMiscAtomOrString(aStringValue
);
1511 nsAtom
* nsAttrValue::GetStoredAtom() const {
1512 if (BaseType() == eAtomBase
) {
1513 return static_cast<nsAtom
*>(GetPtr());
1515 if (BaseType() == eOtherBase
) {
1516 return GetMiscContainer()->GetStoredAtom();
1521 mozilla::StringBuffer
* nsAttrValue::GetStoredStringBuffer() const {
1522 if (BaseType() == eStringBase
) {
1523 return static_cast<mozilla::StringBuffer
*>(GetPtr());
1525 if (BaseType() == eOtherBase
) {
1526 return GetMiscContainer()->GetStoredStringBuffer();
1531 int16_t nsAttrValue::GetEnumTableIndex(const EnumTable
* aTable
) {
1532 int16_t index
= sEnumTableArray
->IndexOf(aTable
);
1534 index
= sEnumTableArray
->Length();
1535 NS_ASSERTION(index
<= NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE
,
1536 "too many enum tables");
1537 sEnumTableArray
->AppendElement(aTable
);
1543 int32_t nsAttrValue::EnumTableEntryToValue(const EnumTable
* aEnumTable
,
1544 const EnumTable
* aTableEntry
) {
1545 int16_t index
= GetEnumTableIndex(aEnumTable
);
1547 (aTableEntry
->value
<< NS_ATTRVALUE_ENUMTABLEINDEX_BITS
) + index
;
1551 bool nsAttrValue::ParseEnumValue(const nsAString
& aValue
,
1552 const EnumTable
* aTable
, bool aCaseSensitive
,
1553 const EnumTable
* aDefaultValue
) {
1555 const EnumTable
* tableEntry
= aTable
;
1557 while (tableEntry
->tag
) {
1558 if (aCaseSensitive
? aValue
.EqualsASCII(tableEntry
->tag
)
1559 : aValue
.LowerCaseEqualsASCII(tableEntry
->tag
)) {
1560 int32_t value
= EnumTableEntryToValue(aTable
, tableEntry
);
1562 bool equals
= aCaseSensitive
|| aValue
.EqualsASCII(tableEntry
->tag
);
1565 tag
.AssignASCII(tableEntry
->tag
);
1566 nsContentUtils::ASCIIToUpper(tag
);
1567 if ((equals
= tag
.Equals(aValue
))) {
1568 value
|= NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER
;
1571 SetIntValueAndType(value
, eEnum
, equals
? nullptr : &aValue
);
1572 NS_ASSERTION(GetEnumValue() == tableEntry
->value
,
1573 "failed to store enum properly");
1580 if (aDefaultValue
) {
1581 MOZ_ASSERT(aTable
<= aDefaultValue
&& aDefaultValue
< tableEntry
,
1582 "aDefaultValue not inside aTable?");
1583 SetIntValueAndType(EnumTableEntryToValue(aTable
, aDefaultValue
), eEnum
,
1591 bool nsAttrValue::DoParseHTMLDimension(const nsAString
& aInput
,
1592 bool aEnsureNonzero
) {
1595 // We don't use nsContentUtils::ParseHTMLInteger here because we
1596 // need a bunch of behavioral differences from it. We _could_ try to
1597 // use it, but it would not be a great fit.
1599 // https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values
1602 const char16_t
* position
= aInput
.BeginReading();
1603 const char16_t
* end
= aInput
.EndReading();
1605 // We will need to keep track of whether this was a canonical representation
1606 // or not. It's non-canonical if it has leading whitespace, leading '+',
1607 // leading '0' characters, or trailing garbage.
1608 bool canonical
= true;
1611 while (position
!= end
&& nsContentUtils::IsHTMLWhitespace(*position
)) {
1612 canonical
= false; // Leading whitespace
1617 if (position
== end
|| *position
< char16_t('0') ||
1618 *position
> char16_t('9')) {
1623 CheckedInt32 value
= 0;
1625 // Collect up leading '0' first to avoid extra branching in the main
1626 // loop to set 'canonical' properly.
1627 while (position
!= end
&& *position
== char16_t('0')) {
1628 canonical
= false; // Leading '0'
1632 // Now collect up other digits.
1633 while (position
!= end
&& *position
>= char16_t('0') &&
1634 *position
<= char16_t('9')) {
1635 value
= value
* 10 + (*position
- char16_t('0'));
1636 if (!value
.isValid()) {
1637 // The spec assumes we can deal with arbitrary-size integers here, but we
1638 // really can't. If someone sets something too big, just bail out and
1645 // Step 6 is implemented implicitly via the various "position != end" guards
1646 // from this point on.
1648 Maybe
<double> doubleValue
;
1649 // Step 7. The return in step 7.2 is handled by just falling through to the
1650 // code below this block when we reach end of input or a non-digit, because
1651 // the while loop will terminate at that point.
1652 if (position
!= end
&& *position
== char16_t('.')) {
1653 canonical
= false; // Let's not rely on double serialization reproducing
1654 // the string we started with.
1657 // If we have a '.' _not_ followed by digits, this is not as efficient as it
1658 // could be, because we will store as a double while we could have stored as
1659 // an int. But that seems like a pretty rare case.
1660 doubleValue
.emplace(value
.value());
1662 double divisor
= 1.0f
;
1664 while (position
!= end
&& *position
>= char16_t('0') &&
1665 *position
<= char16_t('9')) {
1667 divisor
= divisor
* 10.0f
;
1669 doubleValue
.ref() += (*position
- char16_t('0')) / divisor
;
1672 // Step 7.4.4 and 7.4.5 are captured in the while loop condition and the
1673 // "position != end" checks below.
1677 if (aEnsureNonzero
&& value
.value() == 0 &&
1678 (!doubleValue
|| *doubleValue
== 0.0f
)) {
1679 // Not valid. Just drop it.
1683 // Step 8 and the spec's early return from step 7.2.
1685 if (position
!= end
&& *position
== char16_t('%')) {
1688 } else if (doubleValue
) {
1689 type
= eDoubleValue
;
1694 if (position
!= end
) {
1699 MOZ_ASSERT(!canonical
, "We set it false above!");
1700 SetDoubleValueAndType(*doubleValue
, type
, &aInput
);
1702 SetIntValueAndType(value
.value(), type
, canonical
? nullptr : &aInput
);
1708 MOZ_ASSERT(str
== aInput
, "We messed up our 'canonical' boolean!");
1714 bool nsAttrValue::ParseIntWithBounds(const nsAString
& aString
, int32_t aMin
,
1716 MOZ_ASSERT(aMin
< aMax
, "bad boundaries");
1720 nsContentUtils::ParseHTMLIntegerResultFlags result
;
1721 int32_t originalVal
= nsContentUtils::ParseHTMLInteger(aString
, &result
);
1722 if (result
& nsContentUtils::eParseHTMLInteger_Error
) {
1726 int32_t val
= std::max(originalVal
, aMin
);
1727 val
= std::min(val
, aMax
);
1729 (val
!= originalVal
) ||
1730 (result
& nsContentUtils::eParseHTMLInteger_NonStandard
) ||
1731 (result
& nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput
);
1733 SetIntValueAndType(val
, eInteger
, nonStrict
? &aString
: nullptr);
1738 void nsAttrValue::ParseIntWithFallback(const nsAString
& aString
,
1739 int32_t aDefault
, int32_t aMax
) {
1742 nsContentUtils::ParseHTMLIntegerResultFlags result
;
1743 int32_t val
= nsContentUtils::ParseHTMLInteger(aString
, &result
);
1744 bool nonStrict
= false;
1745 if ((result
& nsContentUtils::eParseHTMLInteger_Error
) || val
< 1) {
1755 if ((result
& nsContentUtils::eParseHTMLInteger_NonStandard
) ||
1756 (result
& nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput
)) {
1760 SetIntValueAndType(val
, eInteger
, nonStrict
? &aString
: nullptr);
1763 void nsAttrValue::ParseClampedNonNegativeInt(const nsAString
& aString
,
1764 int32_t aDefault
, int32_t aMin
,
1768 nsContentUtils::ParseHTMLIntegerResultFlags result
;
1769 int32_t val
= nsContentUtils::ParseHTMLInteger(aString
, &result
);
1771 (result
& nsContentUtils::eParseHTMLInteger_NonStandard
) ||
1772 (result
& nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput
);
1774 if (result
& nsContentUtils::eParseHTMLInteger_ErrorOverflow
) {
1775 if (result
& nsContentUtils::eParseHTMLInteger_Negative
) {
1781 } else if ((result
& nsContentUtils::eParseHTMLInteger_Error
) || val
< 0) {
1784 } else if (val
< aMin
) {
1787 } else if (val
> aMax
) {
1792 SetIntValueAndType(val
, eInteger
, nonStrict
? &aString
: nullptr);
1795 bool nsAttrValue::ParseNonNegativeIntValue(const nsAString
& aString
) {
1798 nsContentUtils::ParseHTMLIntegerResultFlags result
;
1799 int32_t originalVal
= nsContentUtils::ParseHTMLInteger(aString
, &result
);
1800 if ((result
& nsContentUtils::eParseHTMLInteger_Error
) || originalVal
< 0) {
1805 (result
& nsContentUtils::eParseHTMLInteger_NonStandard
) ||
1806 (result
& nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput
);
1808 SetIntValueAndType(originalVal
, eInteger
, nonStrict
? &aString
: nullptr);
1813 bool nsAttrValue::ParsePositiveIntValue(const nsAString
& aString
) {
1816 nsContentUtils::ParseHTMLIntegerResultFlags result
;
1817 int32_t originalVal
= nsContentUtils::ParseHTMLInteger(aString
, &result
);
1818 if ((result
& nsContentUtils::eParseHTMLInteger_Error
) || originalVal
<= 0) {
1823 (result
& nsContentUtils::eParseHTMLInteger_NonStandard
) ||
1824 (result
& nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput
);
1826 SetIntValueAndType(originalVal
, eInteger
, nonStrict
? &aString
: nullptr);
1831 bool nsAttrValue::SetColorValue(nscolor aColor
, const nsAString
& aString
) {
1832 mozilla::StringBuffer
* buf
= GetStringBuffer(aString
).take();
1837 MiscContainer
* cont
= EnsureEmptyMiscContainer();
1838 cont
->mValue
.mColor
= aColor
;
1839 cont
->mType
= eColor
;
1841 // Save the literal string we were passed for round-tripping.
1842 cont
->SetStringBitsMainThread(reinterpret_cast<uintptr_t>(buf
) | eStringBase
);
1846 bool nsAttrValue::ParseColor(const nsAString
& aString
) {
1849 // FIXME (partially, at least): HTML5's algorithm says we shouldn't do
1850 // the whitespace compression, trimming, or the test for emptiness.
1851 // (I'm a little skeptical that we shouldn't do the whitespace
1852 // trimming; WebKit also does it.)
1853 nsAutoString
colorStr(aString
);
1854 colorStr
.CompressWhitespace(true, true);
1855 if (colorStr
.IsEmpty()) {
1860 // No color names begin with a '#'; in standards mode, all acceptable
1861 // numeric colors do.
1862 if (colorStr
.First() == '#') {
1863 nsDependentString
withoutHash(colorStr
.get() + 1, colorStr
.Length() - 1);
1864 if (NS_HexToRGBA(withoutHash
, nsHexColorType::NoAlpha
, &color
)) {
1865 return SetColorValue(color
, aString
);
1867 } else if (colorStr
.LowerCaseEqualsLiteral("transparent")) {
1868 return SetColorValue(NS_RGBA(0, 0, 0, 0), aString
);
1870 const NS_ConvertUTF16toUTF8
colorNameU8(colorStr
);
1871 if (Servo_ColorNameToRgb(&colorNameU8
, &color
)) {
1872 return SetColorValue(color
, aString
);
1876 // FIXME (maybe): HTML5 says we should handle system colors. This
1877 // means we probably need another storage type, since we'd need to
1878 // handle dynamic changes. However, I think this is a bad idea:
1879 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-May/026449.html
1881 // Use NS_LooseHexToRGB as a fallback if nothing above worked.
1882 if (NS_LooseHexToRGB(colorStr
, &color
)) {
1883 return SetColorValue(color
, aString
);
1889 bool nsAttrValue::ParseDoubleValue(const nsAString
& aString
) {
1893 double val
= PromiseFlatString(aString
).ToDouble(&ec
);
1894 if (NS_FAILED(ec
)) {
1898 MiscContainer
* cont
= EnsureEmptyMiscContainer();
1899 cont
->mDoubleValue
= val
;
1900 cont
->mType
= eDoubleValue
;
1901 nsAutoString serializedFloat
;
1902 serializedFloat
.AppendFloat(val
);
1903 SetMiscAtomOrString(serializedFloat
.Equals(aString
) ? nullptr : &aString
);
1907 bool nsAttrValue::ParseStyleAttribute(const nsAString
& aString
,
1908 nsIPrincipal
* aMaybeScriptedPrincipal
,
1909 nsStyledElement
* aElement
) {
1910 dom::Document
* doc
= aElement
->OwnerDoc();
1911 AttributeStyles
* attrStyles
= doc
->GetAttributeStyles();
1912 NS_ASSERTION(aElement
->NodePrincipal() == doc
->NodePrincipal(),
1913 "This is unexpected");
1915 nsIPrincipal
* principal
= aMaybeScriptedPrincipal
? aMaybeScriptedPrincipal
1916 : aElement
->NodePrincipal();
1917 RefPtr
<URLExtraData
> data
= aElement
->GetURLDataForStyleAttr(principal
);
1919 // If the (immutable) document URI does not match the element's base URI
1920 // (the common case is that they do match) do not cache the rule. This is
1921 // because the results of the CSS parser are dependent on these URIs, and we
1922 // do not want to have to account for the URIs in the hash lookup.
1923 // Similarly, if the triggering principal does not match the node principal,
1924 // do not cache the rule, since the principal will be encoded in any parsed
1925 // URLs in the rule.
1926 const bool cachingAllowed
= attrStyles
&&
1927 doc
->GetDocumentURI() == data
->BaseURI() &&
1928 principal
== aElement
->NodePrincipal();
1929 if (cachingAllowed
) {
1930 if (MiscContainer
* cont
= attrStyles
->LookupStyleAttr(aString
)) {
1931 // Set our MiscContainer to the cached one.
1933 SetPtrValueAndType(cont
, eOtherBase
);
1938 RefPtr
<DeclarationBlock
> decl
=
1939 DeclarationBlock::FromCssText(aString
, data
, doc
->GetCompatibilityMode(),
1940 doc
->CSSLoader(), StyleCssRuleType::Style
);
1944 decl
->SetAttributeStyles(attrStyles
);
1945 SetTo(decl
.forget(), &aString
);
1947 if (cachingAllowed
) {
1948 MiscContainer
* cont
= GetMiscContainer();
1955 void nsAttrValue::SetMiscAtomOrString(const nsAString
* aValue
) {
1956 NS_ASSERTION(GetMiscContainer(), "Must have MiscContainer!");
1957 NS_ASSERTION(!GetMiscContainer()->mStringBits
|| IsInServoTraversal(),
1958 "Trying to re-set atom or string!");
1960 uint32_t len
= aValue
->Length();
1961 // * We're allowing eCSSDeclaration attributes to store empty
1962 // strings as it can be beneficial to store an empty style
1963 // attribute as a parsed rule.
1964 // * We're allowing enumerated values because sometimes the empty
1965 // string corresponds to a particular enumerated value, especially
1966 // for enumerated values that are not limited enumerated.
1967 // Add other types as needed.
1968 NS_ASSERTION(len
|| Type() == eCSSDeclaration
|| Type() == eEnum
,
1970 MiscContainer
* cont
= GetMiscContainer();
1972 if (len
<= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM
) {
1973 nsAtom
* atom
= MOZ_LIKELY(!IsInServoTraversal())
1974 ? NS_AtomizeMainThread(*aValue
).take()
1975 : NS_Atomize(*aValue
).take();
1976 NS_ENSURE_TRUE_VOID(atom
);
1977 uintptr_t bits
= reinterpret_cast<uintptr_t>(atom
) | eAtomBase
;
1979 // In the common case we're not in the servo traversal, and we can just
1980 // set the bits normally. The parallel case requires more care.
1981 if (MOZ_LIKELY(!IsInServoTraversal())) {
1982 cont
->SetStringBitsMainThread(bits
);
1983 } else if (!cont
->mStringBits
.compareExchange(0, bits
)) {
1984 // We raced with somebody else setting the bits. Release our copy.
1988 mozilla::StringBuffer
* buffer
= GetStringBuffer(*aValue
).take();
1989 NS_ENSURE_TRUE_VOID(buffer
);
1990 uintptr_t bits
= reinterpret_cast<uintptr_t>(buffer
) | eStringBase
;
1992 // In the common case we're not in the servo traversal, and we can just
1993 // set the bits normally. The parallel case requires more care.
1994 if (MOZ_LIKELY(!IsInServoTraversal())) {
1995 cont
->SetStringBitsMainThread(bits
);
1996 } else if (!cont
->mStringBits
.compareExchange(0, bits
)) {
1997 // We raced with somebody else setting the bits. Release our copy.
2004 void nsAttrValue::ResetMiscAtomOrString() {
2005 MiscContainer
* cont
= GetMiscContainer();
2007 if (void* ptr
= cont
->GetStringOrAtomPtr(isString
)) {
2009 static_cast<mozilla::StringBuffer
*>(ptr
)->Release();
2011 static_cast<nsAtom
*>(ptr
)->Release();
2013 cont
->SetStringBitsMainThread(0);
2017 void nsAttrValue::SetSVGType(ValueType aType
, const void* aValue
,
2018 const nsAString
* aSerialized
) {
2019 MOZ_ASSERT(IsSVGType(aType
), "Not an SVG type");
2021 MiscContainer
* cont
= EnsureEmptyMiscContainer();
2022 // All SVG types are just pointers to classes so just setting any of them
2023 // will do. We'll lose type-safety but the signature of the calling
2024 // function should ensure we don't get anything unexpected, and once we
2025 // stick aValue in a union we lose type information anyway.
2026 cont
->mValue
.mSVGLength
= static_cast<const SVGAnimatedLength
*>(aValue
);
2027 cont
->mType
= aType
;
2028 SetMiscAtomOrString(aSerialized
);
2031 MiscContainer
* nsAttrValue::ClearMiscContainer() {
2032 MiscContainer
* cont
= nullptr;
2033 if (BaseType() == eOtherBase
) {
2034 cont
= GetMiscContainer();
2035 if (cont
->IsRefCounted() && cont
->mValue
.mRefCount
> 1) {
2036 // This MiscContainer is shared, we need a new one.
2039 cont
= AllocMiscContainer();
2040 SetPtrValueAndType(cont
, eOtherBase
);
2042 switch (cont
->mType
) {
2043 case eCSSDeclaration
: {
2044 MOZ_ASSERT(cont
->mValue
.mRefCount
== 1);
2047 NS_RELEASE(cont
->mValue
.mCSSDeclaration
);
2050 case eShadowParts
: {
2051 MOZ_ASSERT(cont
->mValue
.mRefCount
== 1);
2053 delete cont
->mValue
.mShadowParts
;
2057 NS_RELEASE(cont
->mValue
.mURL
);
2061 MOZ_ASSERT(cont
->mValue
.mRefCount
== 1);
2064 delete cont
->mValue
.mAtomArray
;
2072 ResetMiscAtomOrString();
2080 MiscContainer
* nsAttrValue::EnsureEmptyMiscContainer() {
2081 MiscContainer
* cont
= ClearMiscContainer();
2083 MOZ_ASSERT(BaseType() == eOtherBase
);
2084 ResetMiscAtomOrString();
2085 cont
= GetMiscContainer();
2087 cont
= AllocMiscContainer();
2088 SetPtrValueAndType(cont
, eOtherBase
);
2094 already_AddRefed
<mozilla::StringBuffer
> nsAttrValue::GetStringBuffer(
2095 const nsAString
& aValue
) const {
2096 uint32_t len
= aValue
.Length();
2100 if (mozilla::StringBuffer
* buf
= aValue
.GetStringBuffer();
2101 buf
&& (buf
->StorageSize() / sizeof(char16_t
) - 1) == len
) {
2102 // We can only reuse the buffer if it's exactly sized, since we rely on
2103 // StorageSize() to get the string length in ToString().
2104 return do_AddRef(buf
);
2106 return mozilla::StringBuffer::Create(aValue
.Data(), aValue
.Length());
2109 size_t nsAttrValue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const {
2112 switch (BaseType()) {
2114 mozilla::StringBuffer
* str
=
2115 static_cast<mozilla::StringBuffer
*>(GetPtr());
2116 n
+= str
? str
->SizeOfIncludingThisIfUnshared(aMallocSizeOf
) : 0;
2120 MiscContainer
* container
= GetMiscContainer();
2124 if (container
->IsRefCounted() && container
->mValue
.mRefCount
> 1) {
2125 // We don't report this MiscContainer at all in order to avoid
2126 // twice-reporting it.
2127 // TODO DMD, bug 1027551 - figure out how to report this ref-counted
2128 // object just once.
2131 n
+= aMallocSizeOf(container
);
2133 // We only count the size of the object pointed by otherPtr if it's a
2134 // string. When it's an atom, it's counted separately.
2135 if (mozilla::StringBuffer
* buf
= container
->GetStoredStringBuffer()) {
2136 n
+= buf
->SizeOfIncludingThisIfUnshared(aMallocSizeOf
);
2139 if (Type() == eCSSDeclaration
&& container
->mValue
.mCSSDeclaration
) {
2140 // TODO: mCSSDeclaration might be owned by another object which
2141 // would make us count them twice, bug 677493.
2142 // Bug 1281964: For DeclarationBlock if we do measure we'll
2143 // need a way to call the Servo heap_size_of function.
2144 // n += container->mCSSDeclaration->SizeOfIncludingThis(aMallocSizeOf);
2145 } else if (Type() == eAtomArray
&& container
->mValue
.mAtomArray
) {
2146 // Don't measure each nsAtom, because they are measured separately.
2147 n
+= container
->mValue
.mAtomArray
->ShallowSizeOfIncludingThis(
2152 case eAtomBase
: // Atoms are counted separately.
2153 case eIntegerBase
: // The value is in mBits, nothing to do.