Backed out changeset b462e7b742d8 (bug 1908261) for causing multiple reftest failures...
[gecko.git] / dom / base / nsAttrValue.cpp
blobf78799fc19a718ea43dcd1419d0a241799786de2
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/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"
33 #include "nsIURI.h"
34 #include "ReferrerInfo.h"
35 #include <algorithm>
37 using namespace mozilla;
39 constexpr uint32_t kMiscContainerCacheSize = 128;
40 static void* gMiscContainerCache[kMiscContainerCacheSize];
41 static uint32_t gMiscContainerCount = 0;
43 /**
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
53 // the MiscContainer.
54 using MapType = nsTHashMap<nsAtom*, MiscContainer*>;
56 static MiscContainer* Lookup(nsAtom* aValue) {
57 if (auto* instance = GetInstance()) {
58 return instance->LookupImpl(aValue);
60 return nullptr;
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);
81 return sInstance;
84 private:
85 MiscContainer* LookupImpl(nsAtom* aValue) {
86 auto lookupResult = mMap.Lookup(aValue);
87 return lookupResult ? *lookupResult : nullptr;
90 void InsertImpl(nsAtom* aValue, MiscContainer* aCont) {
91 MOZ_ASSERT(aCont);
92 mMap.InsertOrUpdate(aValue, aCont);
95 void RemoveImpl(nsAtom* aValue) { mMap.Remove(aValue); }
97 MapType mMap;
100 /* static */
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();
119 /* static */
120 void nsAttrValue::DeallocMiscContainer(MiscContainer* aCont) {
121 MOZ_ASSERT(NS_IsMainThread());
122 if (!aCont) {
123 return;
126 aCont->~MiscContainer();
128 if (gMiscContainerCount < kMiscContainerCacheSize) {
129 gMiscContainerCache[gMiscContainerCount++] = aCont;
130 return;
133 free(aCont);
136 bool MiscContainer::GetString(nsAString& aString) const {
137 bool isString;
138 void* ptr = GetStringOrAtomPtr(isString);
139 if (!ptr) {
140 return false;
142 if (isString) {
143 auto* buffer = static_cast<mozilla::StringBuffer*>(ptr);
144 aString.Assign(buffer, buffer->StorageSize() / sizeof(char16_t) - 1);
145 } else {
146 static_cast<nsAtom*>(ptr)->ToString(aString);
148 return true;
151 void MiscContainer::Cache() {
152 switch (mType) {
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();
160 if (!attrStyles) {
161 return;
164 nsString str;
165 bool gotString = GetString(str);
166 if (!gotString) {
167 return;
170 attrStyles->CacheStyleAttr(str, this);
171 mValue.mCached = 1;
173 // This has to be immutable once it goes into the cache.
174 mValue.mCSSDeclaration->SetImmutable();
175 break;
177 case nsAttrValue::eAtomArray: {
178 MOZ_ASSERT(IsRefCounted());
179 MOZ_ASSERT(mValue.mRefCount > 0);
180 MOZ_ASSERT(!mValue.mCached);
182 nsAtom* atom = GetStoredAtom();
183 if (!atom) {
184 return;
187 AtomArrayCache::Insert(atom, this);
188 mValue.mCached = 1;
189 break;
191 default:
192 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
193 break;
197 void MiscContainer::Evict() {
198 switch (mType) {
199 case nsAttrValue::eCSSDeclaration: {
200 MOZ_ASSERT(IsRefCounted());
201 MOZ_ASSERT(mValue.mRefCount == 0);
203 if (!mValue.mCached) {
204 return;
207 AttributeStyles* attrStyles =
208 mValue.mCSSDeclaration->GetAttributeStyles();
209 MOZ_ASSERT(attrStyles);
211 nsString str;
212 DebugOnly<bool> gotString = GetString(str);
213 MOZ_ASSERT(gotString);
215 attrStyles->EvictStyleAttr(str, this);
216 mValue.mCached = 0;
217 break;
219 case nsAttrValue::eAtomArray: {
220 MOZ_ASSERT(IsRefCounted());
221 MOZ_ASSERT(mValue.mRefCount == 0);
223 if (!mValue.mCached) {
224 return;
227 nsAtom* atom = GetStoredAtom();
228 MOZ_ASSERT(atom);
230 AtomArrayCache::Remove(atom);
232 mValue.mCached = 0;
233 break;
235 default:
237 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
238 break;
242 nsTArray<const nsAttrValue::EnumTable*>* nsAttrValue::sEnumTableArray = nullptr;
244 nsAttrValue::nsAttrValue() : mBits(0) {}
246 nsAttrValue::nsAttrValue(const nsAttrValue& aOther) : mBits(0) {
247 SetTo(aOther);
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)
256 : mBits(0) {
257 SetTo(std::move(aValue), aSerialized);
260 nsAttrValue::~nsAttrValue() { ResetIfSet(); }
262 /* static */
263 void nsAttrValue::Init() {
264 MOZ_ASSERT(!sEnumTableArray, "nsAttrValue already initialized");
265 sEnumTableArray = new nsTArray<const EnumTable*>;
268 /* static */
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()) {
282 case eStringBase: {
283 if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) {
284 str->Release();
286 break;
288 case eOtherBase: {
289 MiscContainer* cont = GetMiscContainer();
290 if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) {
291 NS_RELEASE(cont);
292 break;
295 DeallocMiscContainer(ClearMiscContainer());
297 break;
299 case eAtomBase: {
300 nsAtom* atom = GetAtomValue();
301 NS_RELEASE(atom);
303 break;
305 case eIntegerBase: {
306 break;
310 mBits = 0;
313 void nsAttrValue::SetTo(const nsAttrValue& aOther) {
314 if (this == &aOther) {
315 return;
318 switch (aOther.BaseType()) {
319 case eStringBase: {
320 ResetIfSet();
321 if (auto* str = static_cast<mozilla::StringBuffer*>(aOther.GetPtr())) {
322 str->AddRef();
323 SetPtrValueAndType(str, eStringBase);
325 return;
327 case eOtherBase: {
328 break;
330 case eAtomBase: {
331 ResetIfSet();
332 nsAtom* atom = aOther.GetAtomValue();
333 NS_ADDREF(atom);
334 SetPtrValueAndType(atom, eAtomBase);
335 return;
337 case eIntegerBase: {
338 ResetIfSet();
339 mBits = aOther.mBits;
340 return;
344 MiscContainer* otherCont = aOther.GetMiscContainer();
345 if (otherCont->IsRefCounted()) {
346 DeallocMiscContainer(ClearMiscContainer());
347 NS_ADDREF(otherCont);
348 SetPtrValueAndType(otherCont, eOtherBase);
349 return;
352 MiscContainer* cont = EnsureEmptyMiscContainer();
353 switch (otherCont->mType) {
354 case eInteger: {
355 cont->mValue.mInteger = otherCont->mValue.mInteger;
356 break;
358 case eEnum: {
359 cont->mValue.mEnumValue = otherCont->mValue.mEnumValue;
360 break;
362 case ePercent: {
363 cont->mDoubleValue = otherCont->mDoubleValue;
364 break;
366 case eColor: {
367 cont->mValue.mColor = otherCont->mValue.mColor;
368 break;
370 case eAtomArray:
371 case eShadowParts:
372 case eCSSDeclaration: {
373 MOZ_CRASH("These should be refcounted!");
375 case eURL: {
376 NS_ADDREF(cont->mValue.mURL = otherCont->mValue.mURL);
377 break;
379 case eDoubleValue: {
380 cont->mDoubleValue = otherCont->mDoubleValue;
381 break;
383 default: {
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;
388 } else {
389 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
391 break;
395 bool isString;
396 if (void* otherPtr = otherCont->GetStringOrAtomPtr(isString)) {
397 if (isString) {
398 static_cast<mozilla::StringBuffer*>(otherPtr)->AddRef();
399 } else {
400 static_cast<nsAtom*>(otherPtr)->AddRef();
402 cont->SetStringBitsMainThread(otherCont->mStringBits);
404 // Note, set mType after switch-case, otherwise EnsureEmptyAtomArray doesn't
405 // work correctly.
406 cont->mType = otherCont->mType;
409 void nsAttrValue::SetTo(const nsAString& aValue) {
410 ResetIfSet();
411 mozilla::StringBuffer* buf = GetStringBuffer(aValue).take();
412 if (buf) {
413 SetPtrValueAndType(buf, eStringBase);
417 void nsAttrValue::SetTo(nsAtom* aValue) {
418 ResetIfSet();
419 if (aValue) {
420 NS_ADDREF(aValue);
421 SetPtrValueAndType(aValue, eAtomBase);
425 void nsAttrValue::SetTo(int16_t aInt) {
426 ResetIfSet();
427 SetIntValueAndType(aInt, eInteger, nullptr);
430 void nsAttrValue::SetTo(int32_t aInt, const nsAString* aSerialized) {
431 ResetIfSet();
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;
448 NS_ADDREF(cont);
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);
456 cont->mType = eURL;
457 SetMiscAtomOrString(aSerialized);
460 void nsAttrValue::SetToSerialized(const nsAttrValue& aOther) {
461 if (aOther.Type() != nsAttrValue::eString &&
462 aOther.Type() != nsAttrValue::eAtom) {
463 nsAutoString val;
464 aOther.ToString(val);
465 SetTo(val);
466 } else {
467 SetTo(aOther);
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;
564 mBits = tmp;
567 void nsAttrValue::RemoveDuplicatesFromAtomArray() {
568 if (Type() != eAtomArray) {
569 return;
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.
578 return;
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;
591 if (isString) {
592 static_cast<mozilla::StringBuffer*>(otherPtr)->AddRef();
593 } else {
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;
602 NS_ADDREF(cont);
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)) {
617 return;
621 switch (Type()) {
622 case eString: {
623 if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) {
624 aResult.Assign(str, str->StorageSize() / sizeof(char16_t) - 1);
625 } else {
626 aResult.Truncate();
628 break;
630 case eAtom: {
631 auto* atom = static_cast<nsAtom*>(GetPtr());
632 atom->ToString(aResult);
633 break;
635 case eInteger: {
636 nsAutoString intStr;
637 intStr.AppendInt(GetIntegerValue());
638 aResult = intStr;
640 break;
642 #ifdef DEBUG
643 case eColor: {
644 MOZ_ASSERT_UNREACHABLE("color attribute without string data");
645 aResult.Truncate();
646 break;
648 #endif
649 case eEnum: {
650 GetEnumString(aResult, false);
651 break;
653 case ePercent: {
654 nsAutoString str;
655 if (cont) {
656 str.AppendFloat(cont->mDoubleValue);
657 } else {
658 str.AppendInt(GetIntInternal());
660 aResult = str + u"%"_ns;
662 break;
664 case eCSSDeclaration: {
665 aResult.Truncate();
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);
678 break;
680 case eDoubleValue: {
681 aResult.Truncate();
682 aResult.AppendFloat(GetDoubleValue());
683 break;
685 case eSVGIntegerPair: {
686 SVGAttrValueWrapper::ToString(
687 GetMiscContainer()->mValue.mSVGAnimatedIntegerPair, aResult);
688 break;
690 case eSVGOrient: {
691 SVGAttrValueWrapper::ToString(
692 GetMiscContainer()->mValue.mSVGAnimatedOrient, aResult);
693 break;
695 case eSVGLength: {
696 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLength,
697 aResult);
698 break;
700 case eSVGLengthList: {
701 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLengthList,
702 aResult);
703 break;
705 case eSVGNumberList: {
706 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGNumberList,
707 aResult);
708 break;
710 case eSVGNumberPair: {
711 SVGAttrValueWrapper::ToString(
712 GetMiscContainer()->mValue.mSVGAnimatedNumberPair, aResult);
713 break;
715 case eSVGPathData: {
716 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPathData,
717 aResult);
718 break;
720 case eSVGPointList: {
721 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPointList,
722 aResult);
723 break;
725 case eSVGPreserveAspectRatio: {
726 SVGAttrValueWrapper::ToString(
727 GetMiscContainer()->mValue.mSVGAnimatedPreserveAspectRatio, aResult);
728 break;
730 case eSVGStringList: {
731 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGStringList,
732 aResult);
733 break;
735 case eSVGTransformList: {
736 SVGAttrValueWrapper::ToString(
737 GetMiscContainer()->mValue.mSVGTransformList, aResult);
738 break;
740 case eSVGViewBox: {
741 SVGAttrValueWrapper::ToString(
742 GetMiscContainer()->mValue.mSVGAnimatedViewBox, aResult);
743 break;
745 default: {
746 aResult.Truncate();
747 break;
752 already_AddRefed<nsAtom> nsAttrValue::GetAsAtom() const {
753 switch (Type()) {
754 case eString:
755 return NS_AtomizeMainThread(GetStringValue());
757 case eAtom: {
758 RefPtr<nsAtom> atom = GetAtomValue();
759 return atom.forget();
762 default: {
763 nsAutoString val;
764 ToString(val);
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");
780 return false;
783 aColor = GetMiscContainer()->mValue.mColor;
784 return true;
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);
797 while (table->tag) {
798 if (table->value == val) {
799 aResult.AssignASCII(table->tag);
800 if (!aRealTag &&
801 allEnumBits & NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER) {
802 nsContentUtils::ASCIIToUpper(aResult);
804 return;
806 table++;
809 MOZ_ASSERT_UNREACHABLE("couldn't find value in EnumTable");
812 UniquePtr<AttrAtomArray> AttrAtomArray::CreateDeduplicatedCopyIfDifferentImpl()
813 const {
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)) {
824 filter.add(atom);
825 return false;
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]);
842 continue;
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();
867 if (type == eAtom) {
868 return 1;
871 if (type == eAtomArray) {
872 return GetAtomArrayValue()->mArray.Length();
875 return 0;
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()) {
892 case eStringBase: {
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);
898 return 0;
900 case eOtherBase: {
901 break;
903 case eAtomBase:
904 case eIntegerBase: {
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.
908 return mBits - 0;
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) {
919 case eInteger: {
920 return cont->mValue.mInteger;
922 case eEnum: {
923 return cont->mValue.mEnumValue;
925 case ePercent: {
926 return cont->mDoubleValue;
928 case eColor: {
929 return cont->mValue.mColor;
931 case eCSSDeclaration: {
932 return NS_PTR_TO_INT32(cont->mValue.mCSSDeclaration);
934 case eURL: {
935 nsString str;
936 ToString(str);
937 return HashString(str);
939 case eAtomArray: {
940 uint32_t hash = 0;
941 for (const auto& atom : cont->mValue.mAtomArray->mArray) {
942 hash = AddToHash(hash, atom.get());
944 return hash;
946 case eDoubleValue: {
947 // XXX this is crappy, but oh well
948 return cont->mDoubleValue;
950 default: {
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");
956 return 0;
961 bool nsAttrValue::Equals(const nsAttrValue& aOther) const {
962 if (BaseType() != aOther.BaseType()) {
963 return false;
966 switch (BaseType()) {
967 case eStringBase: {
968 return GetStringValue().Equals(aOther.GetStringValue());
970 case eOtherBase: {
971 break;
973 case eAtomBase:
974 case eIntegerBase: {
975 return mBits == aOther.mBits;
979 MiscContainer* thisCont = GetMiscContainer();
980 MiscContainer* otherCont = aOther.GetMiscContainer();
981 if (thisCont == otherCont) {
982 return true;
985 if (thisCont->mType != otherCont->mType) {
986 return false;
989 bool needsStringComparison = false;
991 switch (thisCont->mType) {
992 case eInteger: {
993 if (thisCont->mValue.mInteger == otherCont->mValue.mInteger) {
994 needsStringComparison = true;
996 break;
998 case eEnum: {
999 if (thisCont->mValue.mEnumValue == otherCont->mValue.mEnumValue) {
1000 needsStringComparison = true;
1002 break;
1004 case ePercent: {
1005 if (thisCont->mDoubleValue == otherCont->mDoubleValue) {
1006 needsStringComparison = true;
1008 break;
1010 case eColor: {
1011 if (thisCont->mValue.mColor == otherCont->mValue.mColor) {
1012 needsStringComparison = true;
1014 break;
1016 case eCSSDeclaration: {
1017 return thisCont->mValue.mCSSDeclaration ==
1018 otherCont->mValue.mCSSDeclaration;
1020 case eURL: {
1021 return thisCont->mValue.mURL == otherCont->mValue.mURL;
1023 case eAtomArray: {
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)) {
1028 return false;
1031 needsStringComparison = true;
1032 break;
1034 case eDoubleValue: {
1035 return thisCont->mDoubleValue == otherCont->mDoubleValue;
1037 default: {
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");
1045 return false;
1047 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
1048 return false;
1051 if (needsStringComparison) {
1052 if (thisCont->mStringBits == otherCont->mStringBits) {
1053 return true;
1055 if ((static_cast<ValueBaseType>(thisCont->mStringBits &
1056 NS_ATTRVALUE_BASETYPE_MASK) ==
1057 eStringBase) &&
1058 (static_cast<ValueBaseType>(otherCont->mStringBits &
1059 NS_ATTRVALUE_BASETYPE_MASK) ==
1060 eStringBase)) {
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))));
1067 return false;
1070 bool nsAttrValue::Equals(const nsAString& aValue,
1071 nsCaseTreatment aCaseSensitive) const {
1072 switch (BaseType()) {
1073 case eStringBase: {
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();
1083 case eAtomBase: {
1084 auto* atom = static_cast<nsAtom*>(GetPtr());
1085 if (aCaseSensitive == eCaseMatters) {
1086 return atom->Equals(aValue);
1088 return nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(atom),
1089 aValue);
1091 default:
1092 break;
1095 nsAutoString val;
1096 ToString(val);
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()) {
1105 case eAtomBase: {
1106 auto* atom = static_cast<nsAtom*>(GetPtr());
1107 if (atom == aValue) {
1108 return true;
1110 if (aCaseSensitive == eCaseMatters) {
1111 return false;
1113 if (atom->IsAsciiLowercase() && aValue->IsAsciiLowercase()) {
1114 return false;
1116 return nsContentUtils::EqualsIgnoreASCIICase(
1117 nsDependentAtomString(atom), nsDependentAtomString(aValue));
1119 case eStringBase: {
1120 if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) {
1121 size_t strLen = str->StorageSize() / sizeof(char16_t) - 1;
1122 if (aValue->GetLength() != strLen) {
1123 return false;
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();
1137 default:
1138 break;
1141 nsAutoString val;
1142 ToString(val);
1143 nsDependentAtomString dep(aValue);
1144 return aCaseSensitive == eCaseMatters
1145 ? val.Equals(dep)
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) {
1155 return false;
1157 return !memcmp(aAttrValue, aSearchValue.BeginReading(),
1158 aSearchValue.Length() * sizeof(char16_t));
1160 return StringBeginsWith(nsDependentString(aAttrValue, aAttrLen),
1161 aSearchValue,
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) {
1172 return false;
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()) {
1189 return true;
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()) {
1204 case eStringBase: {
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,
1208 aCaseSensitive);
1210 return aValue.IsEmpty();
1212 case eAtomBase: {
1213 auto* atom = static_cast<nsAtom*>(GetPtr());
1214 return F::Check(atom->GetUTF16String(), atom->GetLength(), aValue,
1215 aCaseSensitive);
1217 default:
1218 break;
1221 nsAutoString val;
1222 ToString(val);
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()) {
1254 case eAtomBase:
1255 return lhs.Equals(rhs.GetAtomValue(), eCaseMatters);
1257 case eStringBase:
1258 return lhs.Equals(rhs.GetStringValue(), eCaseMatters);
1260 default: {
1261 nsAutoString val;
1262 rhs.ToString(val);
1263 return lhs.Equals(val, eCaseMatters);
1268 bool nsAttrValue::Contains(nsAtom* aValue,
1269 nsCaseTreatment aCaseSensitive) const {
1270 switch (BaseType()) {
1271 case eAtomBase: {
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);
1281 default: {
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
1291 // anyway.
1292 if (nsContentUtils::EqualsIgnoreASCIICase(aValue, cur)) {
1293 return true;
1300 return false;
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()) {
1311 case eAtomBase: {
1312 nsAtom* atom = GetAtomValue();
1313 return atom->Equals(aValue);
1315 default: {
1316 if (Type() == eAtomArray) {
1317 const AttrAtomArray* array = GetAtomArrayValue();
1318 return array->mArray.Contains(aValue, AtomArrayStringComparator());
1323 return false;
1326 void nsAttrValue::ParseAtom(const nsAString& aValue) {
1327 ResetIfSet();
1329 RefPtr<nsAtom> atom = NS_Atomize(aValue);
1330 if (atom) {
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.
1338 NS_ADDREF(cont);
1339 SetPtrValueAndType(cont, eOtherBase);
1340 return;
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)) {
1349 hasSpace = true;
1350 ++iter;
1353 if (iter == end) {
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));
1359 return;
1362 const char16_t* start = iter;
1364 // get first - and often only - atom
1365 do {
1366 ++iter;
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));
1372 if (!classAtom) {
1373 ResetIfSet();
1374 return;
1377 // skip whitespace
1378 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1379 hasSpace = true;
1380 ++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
1386 ResetIfSet();
1387 nsAtom* atom = nullptr;
1388 classAtom.swap(atom);
1389 SetPtrValueAndType(atom, eAtomBase);
1390 return;
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) {
1402 start = iter;
1404 do {
1405 ++iter;
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;
1415 // skip whitespace
1416 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1417 ++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;
1426 NS_ADDREF(cont);
1427 MOZ_ASSERT(cont->mValue.mRefCount == 1);
1429 // Assign the atom to the container's string bits (like SetMiscAtomOrString
1430 // would do).
1431 MOZ_ASSERT(!IsInServoTraversal());
1432 aValue->AddRef();
1433 uintptr_t bits = reinterpret_cast<uintptr_t>(aValue) | eAtomBase;
1434 cont->SetStringBitsMainThread(bits);
1436 // Put the container in the cache.
1437 cont->Cache();
1440 void nsAttrValue::ParseAtomArray(const nsAString& aValue) {
1441 if (aValue.IsVoid()) {
1442 ResetIfSet();
1443 } else {
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) {
1454 ParseAtom(aValue);
1455 } else {
1456 SetTo(aValue);
1460 void nsAttrValue::ParsePartMapping(const nsAString& aValue) {
1461 ResetIfSet();
1462 MiscContainer* cont = EnsureEmptyMiscContainer();
1464 cont->mType = eShadowParts;
1465 cont->mValue.mShadowParts = new ShadowParts(ShadowParts::Parse(aValue));
1466 NS_ADDREF(cont);
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();
1476 switch (aType) {
1477 case eInteger: {
1478 cont->mValue.mInteger = aValue;
1479 break;
1481 case ePercent: {
1482 cont->mDoubleValue = aValue;
1483 break;
1485 case eEnum: {
1486 cont->mValue.mEnumValue = aValue;
1487 break;
1489 default: {
1490 MOZ_ASSERT_UNREACHABLE("unknown integer type");
1491 break;
1494 cont->mType = aType;
1495 SetMiscAtomOrString(aStringValue);
1496 } else {
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();
1518 return nullptr;
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();
1528 return nullptr;
1531 int16_t nsAttrValue::GetEnumTableIndex(const EnumTable* aTable) {
1532 int16_t index = sEnumTableArray->IndexOf(aTable);
1533 if (index < 0) {
1534 index = sEnumTableArray->Length();
1535 NS_ASSERTION(index <= NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE,
1536 "too many enum tables");
1537 sEnumTableArray->AppendElement(aTable);
1540 return index;
1543 int32_t nsAttrValue::EnumTableEntryToValue(const EnumTable* aEnumTable,
1544 const EnumTable* aTableEntry) {
1545 int16_t index = GetEnumTableIndex(aEnumTable);
1546 int32_t value =
1547 (aTableEntry->value << NS_ATTRVALUE_ENUMTABLEINDEX_BITS) + index;
1548 return value;
1551 bool nsAttrValue::ParseEnumValue(const nsAString& aValue,
1552 const EnumTable* aTable, bool aCaseSensitive,
1553 const EnumTable* aDefaultValue) {
1554 ResetIfSet();
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);
1563 if (!equals) {
1564 nsAutoString 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");
1575 return true;
1577 tableEntry++;
1580 if (aDefaultValue) {
1581 MOZ_ASSERT(aTable <= aDefaultValue && aDefaultValue < tableEntry,
1582 "aDefaultValue not inside aTable?");
1583 SetIntValueAndType(EnumTableEntryToValue(aTable, aDefaultValue), eEnum,
1584 &aValue);
1585 return true;
1588 return false;
1591 bool nsAttrValue::DoParseHTMLDimension(const nsAString& aInput,
1592 bool aEnsureNonzero) {
1593 ResetIfSet();
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
1601 // Steps 1 and 2.
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;
1610 // Step 3.
1611 while (position != end && nsContentUtils::IsHTMLWhitespace(*position)) {
1612 canonical = false; // Leading whitespace
1613 ++position;
1616 // Step 4.
1617 if (position == end || *position < char16_t('0') ||
1618 *position > char16_t('9')) {
1619 return false;
1622 // Step 5.
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'
1629 ++position;
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
1639 // ignore it.
1640 return false;
1642 ++position;
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.
1655 // Step 7.1.
1656 ++position;
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());
1661 // Step 7.3.
1662 double divisor = 1.0f;
1663 // Step 7.4.
1664 while (position != end && *position >= char16_t('0') &&
1665 *position <= char16_t('9')) {
1666 // Step 7.4.1.
1667 divisor = divisor * 10.0f;
1668 // Step 7.4.2.
1669 doubleValue.ref() += (*position - char16_t('0')) / divisor;
1670 // Step 7.4.3.
1671 ++position;
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.
1680 return false;
1683 // Step 8 and the spec's early return from step 7.2.
1684 ValueType type;
1685 if (position != end && *position == char16_t('%')) {
1686 type = ePercent;
1687 ++position;
1688 } else if (doubleValue) {
1689 type = eDoubleValue;
1690 } else {
1691 type = eInteger;
1694 if (position != end) {
1695 canonical = false;
1698 if (doubleValue) {
1699 MOZ_ASSERT(!canonical, "We set it false above!");
1700 SetDoubleValueAndType(*doubleValue, type, &aInput);
1701 } else {
1702 SetIntValueAndType(value.value(), type, canonical ? nullptr : &aInput);
1705 #ifdef DEBUG
1706 nsAutoString str;
1707 ToString(str);
1708 MOZ_ASSERT(str == aInput, "We messed up our 'canonical' boolean!");
1709 #endif
1711 return true;
1714 bool nsAttrValue::ParseIntWithBounds(const nsAString& aString, int32_t aMin,
1715 int32_t aMax) {
1716 MOZ_ASSERT(aMin < aMax, "bad boundaries");
1718 ResetIfSet();
1720 nsContentUtils::ParseHTMLIntegerResultFlags result;
1721 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1722 if (result & nsContentUtils::eParseHTMLInteger_Error) {
1723 return false;
1726 int32_t val = std::max(originalVal, aMin);
1727 val = std::min(val, aMax);
1728 bool nonStrict =
1729 (val != originalVal) ||
1730 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1731 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1733 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1735 return true;
1738 void nsAttrValue::ParseIntWithFallback(const nsAString& aString,
1739 int32_t aDefault, int32_t aMax) {
1740 ResetIfSet();
1742 nsContentUtils::ParseHTMLIntegerResultFlags result;
1743 int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
1744 bool nonStrict = false;
1745 if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 1) {
1746 val = aDefault;
1747 nonStrict = true;
1750 if (val > aMax) {
1751 val = aMax;
1752 nonStrict = true;
1755 if ((result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1756 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput)) {
1757 nonStrict = true;
1760 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1763 void nsAttrValue::ParseClampedNonNegativeInt(const nsAString& aString,
1764 int32_t aDefault, int32_t aMin,
1765 int32_t aMax) {
1766 ResetIfSet();
1768 nsContentUtils::ParseHTMLIntegerResultFlags result;
1769 int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
1770 bool nonStrict =
1771 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1772 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1774 if (result & nsContentUtils::eParseHTMLInteger_ErrorOverflow) {
1775 if (result & nsContentUtils::eParseHTMLInteger_Negative) {
1776 val = aDefault;
1777 } else {
1778 val = aMax;
1780 nonStrict = true;
1781 } else if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 0) {
1782 val = aDefault;
1783 nonStrict = true;
1784 } else if (val < aMin) {
1785 val = aMin;
1786 nonStrict = true;
1787 } else if (val > aMax) {
1788 val = aMax;
1789 nonStrict = true;
1792 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1795 bool nsAttrValue::ParseNonNegativeIntValue(const nsAString& aString) {
1796 ResetIfSet();
1798 nsContentUtils::ParseHTMLIntegerResultFlags result;
1799 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1800 if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal < 0) {
1801 return false;
1804 bool nonStrict =
1805 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1806 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1808 SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);
1810 return true;
1813 bool nsAttrValue::ParsePositiveIntValue(const nsAString& aString) {
1814 ResetIfSet();
1816 nsContentUtils::ParseHTMLIntegerResultFlags result;
1817 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1818 if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal <= 0) {
1819 return false;
1822 bool nonStrict =
1823 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1824 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1826 SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);
1828 return true;
1831 bool nsAttrValue::SetColorValue(nscolor aColor, const nsAString& aString) {
1832 mozilla::StringBuffer* buf = GetStringBuffer(aString).take();
1833 if (!buf) {
1834 return false;
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);
1843 return true;
1846 bool nsAttrValue::ParseColor(const nsAString& aString) {
1847 ResetIfSet();
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()) {
1856 return false;
1859 nscolor color;
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);
1869 } else {
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);
1886 return false;
1889 bool nsAttrValue::ParseDoubleValue(const nsAString& aString) {
1890 ResetIfSet();
1892 nsresult ec;
1893 double val = PromiseFlatString(aString).ToDouble(&ec);
1894 if (NS_FAILED(ec)) {
1895 return false;
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);
1904 return true;
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.
1932 NS_ADDREF(cont);
1933 SetPtrValueAndType(cont, eOtherBase);
1934 return true;
1938 RefPtr<DeclarationBlock> decl =
1939 DeclarationBlock::FromCssText(aString, data, doc->GetCompatibilityMode(),
1940 doc->CSSLoader(), StyleCssRuleType::Style);
1941 if (!decl) {
1942 return false;
1944 decl->SetAttributeStyles(attrStyles);
1945 SetTo(decl.forget(), &aString);
1947 if (cachingAllowed) {
1948 MiscContainer* cont = GetMiscContainer();
1949 cont->Cache();
1952 return true;
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!");
1959 if (aValue) {
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,
1969 "Empty string?");
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.
1985 atom->Release();
1987 } else {
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.
1998 buffer->Release();
2004 void nsAttrValue::ResetMiscAtomOrString() {
2005 MiscContainer* cont = GetMiscContainer();
2006 bool isString;
2007 if (void* ptr = cont->GetStringOrAtomPtr(isString)) {
2008 if (isString) {
2009 static_cast<mozilla::StringBuffer*>(ptr)->Release();
2010 } else {
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.
2037 NS_RELEASE(cont);
2039 cont = AllocMiscContainer();
2040 SetPtrValueAndType(cont, eOtherBase);
2041 } else {
2042 switch (cont->mType) {
2043 case eCSSDeclaration: {
2044 MOZ_ASSERT(cont->mValue.mRefCount == 1);
2045 cont->Release();
2046 cont->Evict();
2047 NS_RELEASE(cont->mValue.mCSSDeclaration);
2048 break;
2050 case eShadowParts: {
2051 MOZ_ASSERT(cont->mValue.mRefCount == 1);
2052 cont->Release();
2053 delete cont->mValue.mShadowParts;
2054 break;
2056 case eURL: {
2057 NS_RELEASE(cont->mValue.mURL);
2058 break;
2060 case eAtomArray: {
2061 MOZ_ASSERT(cont->mValue.mRefCount == 1);
2062 cont->Release();
2063 cont->Evict();
2064 delete cont->mValue.mAtomArray;
2065 break;
2067 default: {
2068 break;
2072 ResetMiscAtomOrString();
2073 } else {
2074 ResetIfSet();
2077 return cont;
2080 MiscContainer* nsAttrValue::EnsureEmptyMiscContainer() {
2081 MiscContainer* cont = ClearMiscContainer();
2082 if (cont) {
2083 MOZ_ASSERT(BaseType() == eOtherBase);
2084 ResetMiscAtomOrString();
2085 cont = GetMiscContainer();
2086 } else {
2087 cont = AllocMiscContainer();
2088 SetPtrValueAndType(cont, eOtherBase);
2091 return cont;
2094 already_AddRefed<mozilla::StringBuffer> nsAttrValue::GetStringBuffer(
2095 const nsAString& aValue) const {
2096 uint32_t len = aValue.Length();
2097 if (!len) {
2098 return nullptr;
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 {
2110 size_t n = 0;
2112 switch (BaseType()) {
2113 case eStringBase: {
2114 mozilla::StringBuffer* str =
2115 static_cast<mozilla::StringBuffer*>(GetPtr());
2116 n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0;
2117 break;
2119 case eOtherBase: {
2120 MiscContainer* container = GetMiscContainer();
2121 if (!container) {
2122 break;
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.
2129 break;
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(
2148 aMallocSizeOf);
2150 break;
2152 case eAtomBase: // Atoms are counted separately.
2153 case eIntegerBase: // The value is in mBits, nothing to do.
2154 break;
2157 return n;