Bug 1550519 - Show a translucent parent highlight when a subgrid is highlighted....
[gecko.git] / dom / base / nsAttrValue.cpp
blobc8851087c6b9bea0e004b71cb80e2b50353e4f5d
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * A struct that represents the value (type and actual data) of an
9 * attribute.
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/HashFunctions.h"
15 #include "nsAttrValue.h"
16 #include "nsAttrValueInlines.h"
17 #include "nsAtom.h"
18 #include "nsUnicharUtils.h"
19 #include "mozilla/CORSMode.h"
20 #include "mozilla/MemoryReporting.h"
21 #include "mozilla/ServoBindingTypes.h"
22 #include "mozilla/ServoUtils.h"
23 #include "mozilla/DeclarationBlock.h"
24 #include "nsContentUtils.h"
25 #include "nsReadableUtils.h"
26 #include "nsHTMLCSSStyleSheet.h"
27 #include "nsStyledElement.h"
28 #include "nsIURI.h"
29 #include "mozilla/dom/Document.h"
30 #include <algorithm>
32 using namespace mozilla;
34 #define MISC_STR_PTR(_cont) \
35 reinterpret_cast<void*>((_cont)->mStringBits & NS_ATTRVALUE_POINTERVALUE_MASK)
37 /* static */
38 MiscContainer* nsAttrValue::AllocMiscContainer() {
39 MOZ_ASSERT(NS_IsMainThread());
40 MiscContainer* cont = nullptr;
41 Swap(cont, sMiscContainerCache);
43 if (cont) {
44 return new (cont) MiscContainer;
47 return new MiscContainer;
50 /* static */
51 void nsAttrValue::DeallocMiscContainer(MiscContainer* aCont) {
52 MOZ_ASSERT(NS_IsMainThread());
53 if (!aCont) {
54 return;
57 if (!sMiscContainerCache) {
58 aCont->~MiscContainer();
59 sMiscContainerCache = aCont;
60 } else {
61 delete aCont;
65 bool MiscContainer::GetString(nsAString& aString) const {
66 void* ptr = MISC_STR_PTR(this);
68 if (!ptr) {
69 return false;
72 if (static_cast<nsAttrValue::ValueBaseType>(mStringBits &
73 NS_ATTRVALUE_BASETYPE_MASK) ==
74 nsAttrValue::eStringBase) {
75 nsStringBuffer* buffer = static_cast<nsStringBuffer*>(ptr);
76 if (!buffer) {
77 return false;
80 buffer->ToString(buffer->StorageSize() / sizeof(char16_t) - 1, aString);
81 return true;
84 nsAtom* atom = static_cast<nsAtom*>(ptr);
85 if (!atom) {
86 return false;
89 atom->ToString(aString);
90 return true;
93 void MiscContainer::Cache() {
94 // Not implemented for anything else yet.
95 if (mType != nsAttrValue::eCSSDeclaration) {
96 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
97 return;
100 MOZ_ASSERT(IsRefCounted());
101 MOZ_ASSERT(mValue.mRefCount > 0);
102 MOZ_ASSERT(!mValue.mCached);
104 nsHTMLCSSStyleSheet* sheet = mValue.mCSSDeclaration->GetHTMLCSSStyleSheet();
105 if (!sheet) {
106 return;
109 nsString str;
110 bool gotString = GetString(str);
111 if (!gotString) {
112 return;
115 sheet->CacheStyleAttr(str, this);
116 mValue.mCached = 1;
118 // This has to be immutable once it goes into the cache.
119 mValue.mCSSDeclaration->SetImmutable();
122 void MiscContainer::Evict() {
123 // Not implemented for anything else yet.
124 if (mType != nsAttrValue::eCSSDeclaration) {
125 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
126 return;
128 MOZ_ASSERT(IsRefCounted());
129 MOZ_ASSERT(mValue.mRefCount == 0);
131 if (!mValue.mCached) {
132 return;
135 nsHTMLCSSStyleSheet* sheet = mValue.mCSSDeclaration->GetHTMLCSSStyleSheet();
136 MOZ_ASSERT(sheet);
138 nsString str;
139 DebugOnly<bool> gotString = GetString(str);
140 MOZ_ASSERT(gotString);
142 sheet->EvictStyleAttr(str, this);
143 mValue.mCached = 0;
146 nsTArray<const nsAttrValue::EnumTable*>* nsAttrValue::sEnumTableArray = nullptr;
147 MiscContainer* nsAttrValue::sMiscContainerCache = nullptr;
149 nsAttrValue::nsAttrValue() : mBits(0) {}
151 nsAttrValue::nsAttrValue(const nsAttrValue& aOther) : mBits(0) {
152 SetTo(aOther);
155 nsAttrValue::nsAttrValue(const nsAString& aValue) : mBits(0) { SetTo(aValue); }
157 nsAttrValue::nsAttrValue(nsAtom* aValue) : mBits(0) { SetTo(aValue); }
159 nsAttrValue::nsAttrValue(already_AddRefed<DeclarationBlock> aValue,
160 const nsAString* aSerialized)
161 : mBits(0) {
162 SetTo(std::move(aValue), aSerialized);
165 nsAttrValue::nsAttrValue(const nsIntMargin& aValue) : mBits(0) {
166 SetTo(aValue);
169 nsAttrValue::~nsAttrValue() { ResetIfSet(); }
171 /* static */
172 nsresult nsAttrValue::Init() {
173 NS_ASSERTION(!sEnumTableArray, "nsAttrValue already initialized");
174 sEnumTableArray = new nsTArray<const EnumTable*>;
175 return NS_OK;
178 /* static */
179 void nsAttrValue::Shutdown() {
180 MOZ_ASSERT(NS_IsMainThread());
181 delete sEnumTableArray;
182 sEnumTableArray = nullptr;
183 // The MiscContainer pointed to by sMiscContainerCache has already
184 // be destructed so `delete sMiscContainerCache` is
185 // dangerous. Invoke `operator delete` to free the memory.
186 ::operator delete(sMiscContainerCache);
187 sMiscContainerCache = nullptr;
190 void nsAttrValue::Reset() {
191 switch (BaseType()) {
192 case eStringBase: {
193 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
194 if (str) {
195 str->Release();
198 break;
200 case eOtherBase: {
201 MiscContainer* cont = GetMiscContainer();
202 if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) {
203 NS_RELEASE(cont);
204 break;
207 DeallocMiscContainer(ClearMiscContainer());
209 break;
211 case eAtomBase: {
212 nsAtom* atom = GetAtomValue();
213 NS_RELEASE(atom);
215 break;
217 case eIntegerBase: {
218 break;
222 mBits = 0;
225 void nsAttrValue::SetTo(const nsAttrValue& aOther) {
226 if (this == &aOther) {
227 return;
230 switch (aOther.BaseType()) {
231 case eStringBase: {
232 ResetIfSet();
233 nsStringBuffer* str = static_cast<nsStringBuffer*>(aOther.GetPtr());
234 if (str) {
235 str->AddRef();
236 SetPtrValueAndType(str, eStringBase);
238 return;
240 case eOtherBase: {
241 break;
243 case eAtomBase: {
244 ResetIfSet();
245 nsAtom* atom = aOther.GetAtomValue();
246 NS_ADDREF(atom);
247 SetPtrValueAndType(atom, eAtomBase);
248 return;
250 case eIntegerBase: {
251 ResetIfSet();
252 mBits = aOther.mBits;
253 return;
257 MiscContainer* otherCont = aOther.GetMiscContainer();
258 if (otherCont->IsRefCounted()) {
259 DeallocMiscContainer(ClearMiscContainer());
260 NS_ADDREF(otherCont);
261 SetPtrValueAndType(otherCont, eOtherBase);
262 return;
265 MiscContainer* cont = EnsureEmptyMiscContainer();
266 switch (otherCont->mType) {
267 case eInteger: {
268 cont->mValue.mInteger = otherCont->mValue.mInteger;
269 break;
271 case eEnum: {
272 cont->mValue.mEnumValue = otherCont->mValue.mEnumValue;
273 break;
275 case ePercent: {
276 cont->mValue.mPercent = otherCont->mValue.mPercent;
277 break;
279 case eColor: {
280 cont->mValue.mColor = otherCont->mValue.mColor;
281 break;
283 case eCSSDeclaration: {
284 MOZ_CRASH("These should be refcounted!");
286 case eURL: {
287 NS_ADDREF(cont->mValue.mURL = otherCont->mValue.mURL);
288 break;
290 case eAtomArray: {
291 if (!EnsureEmptyAtomArray() ||
292 !GetAtomArrayValue()->AppendElements(*otherCont->mValue.mAtomArray)) {
293 Reset();
294 return;
296 break;
298 case eDoubleValue: {
299 cont->mDoubleValue = otherCont->mDoubleValue;
300 break;
302 case eIntMarginValue: {
303 if (otherCont->mValue.mIntMargin)
304 cont->mValue.mIntMargin =
305 new nsIntMargin(*otherCont->mValue.mIntMargin);
306 break;
308 default: {
309 if (IsSVGType(otherCont->mType)) {
310 // All SVG types are just pointers to classes and will therefore have
311 // the same size so it doesn't really matter which one we assign
312 cont->mValue.mSVGLength = otherCont->mValue.mSVGLength;
313 } else {
314 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
316 break;
320 void* otherPtr = MISC_STR_PTR(otherCont);
321 if (otherPtr) {
322 if (static_cast<ValueBaseType>(otherCont->mStringBits &
323 NS_ATTRVALUE_BASETYPE_MASK) == eStringBase) {
324 static_cast<nsStringBuffer*>(otherPtr)->AddRef();
325 } else {
326 static_cast<nsAtom*>(otherPtr)->AddRef();
328 cont->SetStringBitsMainThread(otherCont->mStringBits);
330 // Note, set mType after switch-case, otherwise EnsureEmptyAtomArray doesn't
331 // work correctly.
332 cont->mType = otherCont->mType;
335 void nsAttrValue::SetTo(const nsAString& aValue) {
336 ResetIfSet();
337 nsStringBuffer* buf = GetStringBuffer(aValue).take();
338 if (buf) {
339 SetPtrValueAndType(buf, eStringBase);
343 void nsAttrValue::SetTo(nsAtom* aValue) {
344 ResetIfSet();
345 if (aValue) {
346 NS_ADDREF(aValue);
347 SetPtrValueAndType(aValue, eAtomBase);
351 void nsAttrValue::SetTo(int16_t aInt) {
352 ResetIfSet();
353 SetIntValueAndType(aInt, eInteger, nullptr);
356 void nsAttrValue::SetTo(int32_t aInt, const nsAString* aSerialized) {
357 ResetIfSet();
358 SetIntValueAndType(aInt, eInteger, aSerialized);
361 void nsAttrValue::SetTo(double aValue, const nsAString* aSerialized) {
362 MiscContainer* cont = EnsureEmptyMiscContainer();
363 cont->mDoubleValue = aValue;
364 cont->mType = eDoubleValue;
365 SetMiscAtomOrString(aSerialized);
368 void nsAttrValue::SetTo(already_AddRefed<DeclarationBlock> aValue,
369 const nsAString* aSerialized) {
370 MiscContainer* cont = EnsureEmptyMiscContainer();
371 MOZ_ASSERT(cont->mValue.mRefCount == 0);
372 cont->mValue.mCSSDeclaration = aValue.take();
373 cont->mType = eCSSDeclaration;
374 NS_ADDREF(cont);
375 SetMiscAtomOrString(aSerialized);
376 MOZ_ASSERT(cont->mValue.mRefCount == 1);
379 void nsAttrValue::SetTo(nsIURI* aValue, const nsAString* aSerialized) {
380 MiscContainer* cont = EnsureEmptyMiscContainer();
381 NS_ADDREF(cont->mValue.mURL = aValue);
382 cont->mType = eURL;
383 SetMiscAtomOrString(aSerialized);
386 void nsAttrValue::SetTo(const nsIntMargin& aValue) {
387 MiscContainer* cont = EnsureEmptyMiscContainer();
388 cont->mValue.mIntMargin = new nsIntMargin(aValue);
389 cont->mType = eIntMarginValue;
392 void nsAttrValue::SetToSerialized(const nsAttrValue& aOther) {
393 if (aOther.Type() != nsAttrValue::eString &&
394 aOther.Type() != nsAttrValue::eAtom) {
395 nsAutoString val;
396 aOther.ToString(val);
397 SetTo(val);
398 } else {
399 SetTo(aOther);
403 void nsAttrValue::SetTo(const SVGAnimatedOrient& aValue,
404 const nsAString* aSerialized) {
405 SetSVGType(eSVGOrient, &aValue, aSerialized);
408 void nsAttrValue::SetTo(const SVGAnimatedIntegerPair& aValue,
409 const nsAString* aSerialized) {
410 SetSVGType(eSVGIntegerPair, &aValue, aSerialized);
413 void nsAttrValue::SetTo(const SVGAnimatedLength& aValue,
414 const nsAString* aSerialized) {
415 SetSVGType(eSVGLength, &aValue, aSerialized);
418 void nsAttrValue::SetTo(const SVGLengthList& aValue,
419 const nsAString* aSerialized) {
420 // While an empty string will parse as a length list, there's no need to store
421 // it (and SetMiscAtomOrString will assert if we try)
422 if (aSerialized && aSerialized->IsEmpty()) {
423 aSerialized = nullptr;
425 SetSVGType(eSVGLengthList, &aValue, aSerialized);
428 void nsAttrValue::SetTo(const SVGNumberList& aValue,
429 const nsAString* aSerialized) {
430 // While an empty string will parse as a number list, there's no need to store
431 // it (and SetMiscAtomOrString will assert if we try)
432 if (aSerialized && aSerialized->IsEmpty()) {
433 aSerialized = nullptr;
435 SetSVGType(eSVGNumberList, &aValue, aSerialized);
438 void nsAttrValue::SetTo(const SVGAnimatedNumberPair& aValue,
439 const nsAString* aSerialized) {
440 SetSVGType(eSVGNumberPair, &aValue, aSerialized);
443 void nsAttrValue::SetTo(const SVGPathData& aValue,
444 const nsAString* aSerialized) {
445 // While an empty string will parse as path data, there's no need to store it
446 // (and SetMiscAtomOrString will assert if we try)
447 if (aSerialized && aSerialized->IsEmpty()) {
448 aSerialized = nullptr;
450 SetSVGType(eSVGPathData, &aValue, aSerialized);
453 void nsAttrValue::SetTo(const SVGPointList& aValue,
454 const nsAString* aSerialized) {
455 // While an empty string will parse as a point list, there's no need to store
456 // it (and SetMiscAtomOrString will assert if we try)
457 if (aSerialized && aSerialized->IsEmpty()) {
458 aSerialized = nullptr;
460 SetSVGType(eSVGPointList, &aValue, aSerialized);
463 void nsAttrValue::SetTo(const SVGAnimatedPreserveAspectRatio& aValue,
464 const nsAString* aSerialized) {
465 SetSVGType(eSVGPreserveAspectRatio, &aValue, aSerialized);
468 void nsAttrValue::SetTo(const SVGStringList& aValue,
469 const nsAString* aSerialized) {
470 // While an empty string will parse as a string list, there's no need to store
471 // it (and SetMiscAtomOrString will assert if we try)
472 if (aSerialized && aSerialized->IsEmpty()) {
473 aSerialized = nullptr;
475 SetSVGType(eSVGStringList, &aValue, aSerialized);
478 void nsAttrValue::SetTo(const SVGTransformList& aValue,
479 const nsAString* aSerialized) {
480 // While an empty string will parse as a transform list, there's no need to
481 // store it (and SetMiscAtomOrString will assert if we try)
482 if (aSerialized && aSerialized->IsEmpty()) {
483 aSerialized = nullptr;
485 SetSVGType(eSVGTransformList, &aValue, aSerialized);
488 void nsAttrValue::SetTo(const SVGAnimatedViewBox& aValue,
489 const nsAString* aSerialized) {
490 SetSVGType(eSVGViewBox, &aValue, aSerialized);
493 void nsAttrValue::SwapValueWith(nsAttrValue& aOther) {
494 uintptr_t tmp = aOther.mBits;
495 aOther.mBits = mBits;
496 mBits = tmp;
499 void nsAttrValue::ToString(nsAString& aResult) const {
500 MiscContainer* cont = nullptr;
501 if (BaseType() == eOtherBase) {
502 cont = GetMiscContainer();
504 if (cont->GetString(aResult)) {
505 return;
509 switch (Type()) {
510 case eString: {
511 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
512 if (str) {
513 str->ToString(str->StorageSize() / sizeof(char16_t) - 1, aResult);
514 } else {
515 aResult.Truncate();
517 break;
519 case eAtom: {
520 nsAtom* atom = static_cast<nsAtom*>(GetPtr());
521 atom->ToString(aResult);
523 break;
525 case eInteger: {
526 nsAutoString intStr;
527 intStr.AppendInt(GetIntegerValue());
528 aResult = intStr;
530 break;
532 #ifdef DEBUG
533 case eColor: {
534 MOZ_ASSERT_UNREACHABLE("color attribute without string data");
535 aResult.Truncate();
536 break;
538 #endif
539 case eEnum: {
540 GetEnumString(aResult, false);
541 break;
543 case ePercent: {
544 nsAutoString intStr;
545 intStr.AppendInt(cont ? cont->mValue.mPercent : GetIntInternal());
546 aResult = intStr + NS_LITERAL_STRING("%");
548 break;
550 case eCSSDeclaration: {
551 aResult.Truncate();
552 MiscContainer* container = GetMiscContainer();
553 if (DeclarationBlock* decl = container->mValue.mCSSDeclaration) {
554 decl->ToString(aResult);
557 // This can be reached during parallel selector matching with attribute
558 // selectors on the style attribute. SetMiscAtomOrString handles this
559 // case, and as of this writing this is the only consumer that needs it.
560 const_cast<nsAttrValue*>(this)->SetMiscAtomOrString(&aResult);
562 break;
564 case eDoubleValue: {
565 aResult.Truncate();
566 aResult.AppendFloat(GetDoubleValue());
567 break;
569 case eSVGIntegerPair: {
570 SVGAttrValueWrapper::ToString(
571 GetMiscContainer()->mValue.mSVGAnimatedIntegerPair, aResult);
572 break;
574 case eSVGOrient: {
575 SVGAttrValueWrapper::ToString(
576 GetMiscContainer()->mValue.mSVGAnimatedOrient, aResult);
577 break;
579 case eSVGLength: {
580 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLength,
581 aResult);
582 break;
584 case eSVGLengthList: {
585 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLengthList,
586 aResult);
587 break;
589 case eSVGNumberList: {
590 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGNumberList,
591 aResult);
592 break;
594 case eSVGNumberPair: {
595 SVGAttrValueWrapper::ToString(
596 GetMiscContainer()->mValue.mSVGAnimatedNumberPair, aResult);
597 break;
599 case eSVGPathData: {
600 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPathData,
601 aResult);
602 break;
604 case eSVGPointList: {
605 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPointList,
606 aResult);
607 break;
609 case eSVGPreserveAspectRatio: {
610 SVGAttrValueWrapper::ToString(
611 GetMiscContainer()->mValue.mSVGAnimatedPreserveAspectRatio, aResult);
612 break;
614 case eSVGStringList: {
615 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGStringList,
616 aResult);
617 break;
619 case eSVGTransformList: {
620 SVGAttrValueWrapper::ToString(
621 GetMiscContainer()->mValue.mSVGTransformList, aResult);
622 break;
624 case eSVGViewBox: {
625 SVGAttrValueWrapper::ToString(
626 GetMiscContainer()->mValue.mSVGAnimatedViewBox, aResult);
627 break;
629 default: {
630 aResult.Truncate();
631 break;
636 already_AddRefed<nsAtom> nsAttrValue::GetAsAtom() const {
637 switch (Type()) {
638 case eString:
639 return NS_AtomizeMainThread(GetStringValue());
641 case eAtom: {
642 RefPtr<nsAtom> atom = GetAtomValue();
643 return atom.forget();
646 default: {
647 nsAutoString val;
648 ToString(val);
649 return NS_AtomizeMainThread(val);
654 const nsCheapString nsAttrValue::GetStringValue() const {
655 MOZ_ASSERT(Type() == eString, "wrong type");
657 return nsCheapString(static_cast<nsStringBuffer*>(GetPtr()));
660 bool nsAttrValue::GetColorValue(nscolor& aColor) const {
661 if (Type() != eColor) {
662 // Unparseable value, treat as unset.
663 NS_ASSERTION(Type() == eString, "unexpected type for color-valued attr");
664 return false;
667 aColor = GetMiscContainer()->mValue.mColor;
668 return true;
671 void nsAttrValue::GetEnumString(nsAString& aResult, bool aRealTag) const {
672 MOZ_ASSERT(Type() == eEnum, "wrong type");
674 uint32_t allEnumBits = (BaseType() == eIntegerBase)
675 ? static_cast<uint32_t>(GetIntInternal())
676 : GetMiscContainer()->mValue.mEnumValue;
677 int16_t val = allEnumBits >> NS_ATTRVALUE_ENUMTABLEINDEX_BITS;
678 const EnumTable* table = sEnumTableArray->ElementAt(
679 allEnumBits & NS_ATTRVALUE_ENUMTABLEINDEX_MASK);
681 while (table->tag) {
682 if (table->value == val) {
683 aResult.AssignASCII(table->tag);
684 if (!aRealTag &&
685 allEnumBits & NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER) {
686 nsContentUtils::ASCIIToUpper(aResult);
688 return;
690 table++;
693 MOZ_ASSERT_UNREACHABLE("couldn't find value in EnumTable");
696 uint32_t nsAttrValue::GetAtomCount() const {
697 ValueType type = Type();
699 if (type == eAtom) {
700 return 1;
703 if (type == eAtomArray) {
704 return GetAtomArrayValue()->Length();
707 return 0;
710 nsAtom* nsAttrValue::AtomAt(int32_t aIndex) const {
711 MOZ_ASSERT(aIndex >= 0, "Index must not be negative");
712 MOZ_ASSERT(GetAtomCount() > uint32_t(aIndex), "aIndex out of range");
714 if (BaseType() == eAtomBase) {
715 return GetAtomValue();
718 NS_ASSERTION(Type() == eAtomArray, "GetAtomCount must be confused");
720 return GetAtomArrayValue()->ElementAt(aIndex);
723 uint32_t nsAttrValue::HashValue() const {
724 switch (BaseType()) {
725 case eStringBase: {
726 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
727 if (str) {
728 uint32_t len = str->StorageSize() / sizeof(char16_t) - 1;
729 return HashString(static_cast<char16_t*>(str->Data()), len);
732 return 0;
734 case eOtherBase: {
735 break;
737 case eAtomBase:
738 case eIntegerBase: {
739 // mBits and uint32_t might have different size. This should silence
740 // any warnings or compile-errors. This is what the implementation of
741 // NS_PTR_TO_INT32 does to take care of the same problem.
742 return mBits - 0;
746 MiscContainer* cont = GetMiscContainer();
747 if (static_cast<ValueBaseType>(cont->mStringBits &
748 NS_ATTRVALUE_BASETYPE_MASK) == eAtomBase) {
749 return cont->mStringBits - 0;
752 switch (cont->mType) {
753 case eInteger: {
754 return cont->mValue.mInteger;
756 case eEnum: {
757 return cont->mValue.mEnumValue;
759 case ePercent: {
760 return cont->mValue.mPercent;
762 case eColor: {
763 return cont->mValue.mColor;
765 case eCSSDeclaration: {
766 return NS_PTR_TO_INT32(cont->mValue.mCSSDeclaration);
768 case eURL: {
769 nsString str;
770 ToString(str);
771 return HashString(str);
773 case eAtomArray: {
774 uint32_t hash = 0;
775 uint32_t count = cont->mValue.mAtomArray->Length();
776 for (RefPtr<nsAtom>*cur = cont->mValue.mAtomArray->Elements(),
777 *end = cur + count;
778 cur != end; ++cur) {
779 hash = AddToHash(hash, cur->get());
781 return hash;
783 case eDoubleValue: {
784 // XXX this is crappy, but oh well
785 return cont->mDoubleValue;
787 case eIntMarginValue: {
788 return NS_PTR_TO_INT32(cont->mValue.mIntMargin);
790 default: {
791 if (IsSVGType(cont->mType)) {
792 // All SVG types are just pointers to classes so we can treat them alike
793 return NS_PTR_TO_INT32(cont->mValue.mSVGLength);
795 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
796 return 0;
801 bool nsAttrValue::Equals(const nsAttrValue& aOther) const {
802 if (BaseType() != aOther.BaseType()) {
803 return false;
806 switch (BaseType()) {
807 case eStringBase: {
808 return GetStringValue().Equals(aOther.GetStringValue());
810 case eOtherBase: {
811 break;
813 case eAtomBase:
814 case eIntegerBase: {
815 return mBits == aOther.mBits;
819 MiscContainer* thisCont = GetMiscContainer();
820 MiscContainer* otherCont = aOther.GetMiscContainer();
821 if (thisCont == otherCont) {
822 return true;
825 if (thisCont->mType != otherCont->mType) {
826 return false;
829 bool needsStringComparison = false;
831 switch (thisCont->mType) {
832 case eInteger: {
833 if (thisCont->mValue.mInteger == otherCont->mValue.mInteger) {
834 needsStringComparison = true;
836 break;
838 case eEnum: {
839 if (thisCont->mValue.mEnumValue == otherCont->mValue.mEnumValue) {
840 needsStringComparison = true;
842 break;
844 case ePercent: {
845 if (thisCont->mValue.mPercent == otherCont->mValue.mPercent) {
846 needsStringComparison = true;
848 break;
850 case eColor: {
851 if (thisCont->mValue.mColor == otherCont->mValue.mColor) {
852 needsStringComparison = true;
854 break;
856 case eCSSDeclaration: {
857 return thisCont->mValue.mCSSDeclaration ==
858 otherCont->mValue.mCSSDeclaration;
860 case eURL: {
861 return thisCont->mValue.mURL == otherCont->mValue.mURL;
863 case eAtomArray: {
864 // For classlists we could be insensitive to order, however
865 // classlists are never mapped attributes so they are never compared.
867 if (!(*thisCont->mValue.mAtomArray == *otherCont->mValue.mAtomArray)) {
868 return false;
871 needsStringComparison = true;
872 break;
874 case eDoubleValue: {
875 return thisCont->mDoubleValue == otherCont->mDoubleValue;
877 case eIntMarginValue: {
878 return thisCont->mValue.mIntMargin == otherCont->mValue.mIntMargin;
880 default: {
881 if (IsSVGType(thisCont->mType)) {
882 // Currently this method is never called for nsAttrValue objects that
883 // point to SVG data types.
884 // If that changes then we probably want to add methods to the
885 // corresponding SVG types to compare their base values.
886 // As a shortcut, however, we can begin by comparing the pointers.
887 MOZ_ASSERT(false, "Comparing nsAttrValues that point to SVG data");
888 return false;
890 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
891 return false;
894 if (needsStringComparison) {
895 if (thisCont->mStringBits == otherCont->mStringBits) {
896 return true;
898 if ((static_cast<ValueBaseType>(thisCont->mStringBits &
899 NS_ATTRVALUE_BASETYPE_MASK) ==
900 eStringBase) &&
901 (static_cast<ValueBaseType>(otherCont->mStringBits &
902 NS_ATTRVALUE_BASETYPE_MASK) ==
903 eStringBase)) {
904 return nsCheapString(reinterpret_cast<nsStringBuffer*>(
905 static_cast<uintptr_t>(thisCont->mStringBits)))
906 .Equals(nsCheapString(reinterpret_cast<nsStringBuffer*>(
907 static_cast<uintptr_t>(otherCont->mStringBits))));
910 return false;
913 bool nsAttrValue::Equals(const nsAString& aValue,
914 nsCaseTreatment aCaseSensitive) const {
915 switch (BaseType()) {
916 case eStringBase: {
917 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
918 if (str) {
919 nsDependentString dep(static_cast<char16_t*>(str->Data()),
920 str->StorageSize() / sizeof(char16_t) - 1);
921 return aCaseSensitive == eCaseMatters
922 ? aValue.Equals(dep)
923 : nsContentUtils::EqualsIgnoreASCIICase(aValue, dep);
925 return aValue.IsEmpty();
927 case eAtomBase:
928 if (aCaseSensitive == eCaseMatters) {
929 return static_cast<nsAtom*>(GetPtr())->Equals(aValue);
931 return nsContentUtils::EqualsIgnoreASCIICase(
932 nsDependentAtomString(static_cast<nsAtom*>(GetPtr())), aValue);
933 default:
934 break;
937 nsAutoString val;
938 ToString(val);
939 return aCaseSensitive == eCaseMatters
940 ? val.Equals(aValue)
941 : nsContentUtils::EqualsIgnoreASCIICase(val, aValue);
944 bool nsAttrValue::Equals(const nsAtom* aValue,
945 nsCaseTreatment aCaseSensitive) const {
946 if (aCaseSensitive != eCaseMatters) {
947 // Need a better way to handle this!
948 nsAutoString value;
949 aValue->ToString(value);
950 return Equals(value, aCaseSensitive);
953 switch (BaseType()) {
954 case eStringBase: {
955 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
956 if (str) {
957 nsDependentString dep(static_cast<char16_t*>(str->Data()),
958 str->StorageSize() / sizeof(char16_t) - 1);
959 return aValue->Equals(dep);
961 return aValue == nsGkAtoms::_empty;
963 case eAtomBase: {
964 return static_cast<nsAtom*>(GetPtr()) == aValue;
966 default:
967 break;
970 nsAutoString val;
971 ToString(val);
972 return aValue->Equals(val);
975 bool nsAttrValue::EqualsAsStrings(const nsAttrValue& aOther) const {
976 if (Type() == aOther.Type()) {
977 return Equals(aOther);
980 // We need to serialize at least one nsAttrValue before passing to
981 // Equals(const nsAString&), but we can avoid unnecessarily serializing both
982 // by checking if one is already of a string type.
983 bool thisIsString = (BaseType() == eStringBase || BaseType() == eAtomBase);
984 const nsAttrValue& lhs = thisIsString ? *this : aOther;
985 const nsAttrValue& rhs = thisIsString ? aOther : *this;
987 switch (rhs.BaseType()) {
988 case eAtomBase:
989 return lhs.Equals(rhs.GetAtomValue(), eCaseMatters);
991 case eStringBase:
992 return lhs.Equals(rhs.GetStringValue(), eCaseMatters);
994 default: {
995 nsAutoString val;
996 rhs.ToString(val);
997 return lhs.Equals(val, eCaseMatters);
1002 bool nsAttrValue::Contains(nsAtom* aValue,
1003 nsCaseTreatment aCaseSensitive) const {
1004 switch (BaseType()) {
1005 case eAtomBase: {
1006 nsAtom* atom = GetAtomValue();
1007 if (aCaseSensitive == eCaseMatters) {
1008 return aValue == atom;
1011 // For performance reasons, don't do a full on unicode case insensitive
1012 // string comparison. This is only used for quirks mode anyway.
1013 return nsContentUtils::EqualsIgnoreASCIICase(aValue, atom);
1015 default: {
1016 if (Type() == eAtomArray) {
1017 AtomArray* array = GetAtomArrayValue();
1018 if (aCaseSensitive == eCaseMatters) {
1019 return array->Contains(aValue);
1022 for (RefPtr<nsAtom>& cur : *array) {
1023 // For performance reasons, don't do a full on unicode case
1024 // insensitive string comparison. This is only used for quirks mode
1025 // anyway.
1026 if (nsContentUtils::EqualsIgnoreASCIICase(aValue, cur)) {
1027 return true;
1034 return false;
1037 struct AtomArrayStringComparator {
1038 bool Equals(nsAtom* atom, const nsAString& string) const {
1039 return atom->Equals(string);
1043 bool nsAttrValue::Contains(const nsAString& aValue) const {
1044 switch (BaseType()) {
1045 case eAtomBase: {
1046 nsAtom* atom = GetAtomValue();
1047 return atom->Equals(aValue);
1049 default: {
1050 if (Type() == eAtomArray) {
1051 AtomArray* array = GetAtomArrayValue();
1052 return array->Contains(aValue, AtomArrayStringComparator());
1057 return false;
1060 void nsAttrValue::ParseAtom(const nsAString& aValue) {
1061 ResetIfSet();
1063 RefPtr<nsAtom> atom = NS_Atomize(aValue);
1064 if (atom) {
1065 SetPtrValueAndType(atom.forget().take(), eAtomBase);
1069 void nsAttrValue::ParseAtomArray(const nsAString& aValue) {
1070 nsAString::const_iterator iter, end;
1071 aValue.BeginReading(iter);
1072 aValue.EndReading(end);
1073 bool hasSpace = false;
1075 // skip initial whitespace
1076 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1077 hasSpace = true;
1078 ++iter;
1081 if (iter == end) {
1082 SetTo(aValue);
1083 return;
1086 nsAString::const_iterator start(iter);
1088 // get first - and often only - atom
1089 do {
1090 ++iter;
1091 } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));
1093 RefPtr<nsAtom> classAtom = NS_AtomizeMainThread(Substring(start, iter));
1094 if (!classAtom) {
1095 Reset();
1096 return;
1099 // skip whitespace
1100 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1101 hasSpace = true;
1102 ++iter;
1105 if (iter == end && !hasSpace) {
1106 // we only found one classname and there was no whitespace so
1107 // don't bother storing a list
1108 ResetIfSet();
1109 nsAtom* atom = nullptr;
1110 classAtom.swap(atom);
1111 SetPtrValueAndType(atom, eAtomBase);
1112 return;
1115 if (!EnsureEmptyAtomArray()) {
1116 return;
1119 AtomArray* array = GetAtomArrayValue();
1121 if (!array->AppendElement(std::move(classAtom))) {
1122 Reset();
1123 return;
1126 // parse the rest of the classnames
1127 while (iter != end) {
1128 start = iter;
1130 do {
1131 ++iter;
1132 } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));
1134 classAtom = NS_AtomizeMainThread(Substring(start, iter));
1136 if (!array->AppendElement(std::move(classAtom))) {
1137 Reset();
1138 return;
1141 // skip whitespace
1142 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1143 ++iter;
1147 SetMiscAtomOrString(&aValue);
1150 void nsAttrValue::ParseStringOrAtom(const nsAString& aValue) {
1151 uint32_t len = aValue.Length();
1152 // Don't bother with atoms if it's an empty string since
1153 // we can store those efficently anyway.
1154 if (len && len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) {
1155 ParseAtom(aValue);
1156 } else {
1157 SetTo(aValue);
1161 void nsAttrValue::SetIntValueAndType(int32_t aValue, ValueType aType,
1162 const nsAString* aStringValue) {
1163 if (aStringValue || aValue > NS_ATTRVALUE_INTEGERTYPE_MAXVALUE ||
1164 aValue < NS_ATTRVALUE_INTEGERTYPE_MINVALUE) {
1165 MiscContainer* cont = EnsureEmptyMiscContainer();
1166 switch (aType) {
1167 case eInteger: {
1168 cont->mValue.mInteger = aValue;
1169 break;
1171 case ePercent: {
1172 cont->mValue.mPercent = aValue;
1173 break;
1175 case eEnum: {
1176 cont->mValue.mEnumValue = aValue;
1177 break;
1179 default: {
1180 MOZ_ASSERT_UNREACHABLE("unknown integer type");
1181 break;
1184 cont->mType = aType;
1185 SetMiscAtomOrString(aStringValue);
1186 } else {
1187 NS_ASSERTION(!mBits, "Reset before calling SetIntValueAndType!");
1188 mBits = (aValue * NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER) | aType;
1192 int16_t nsAttrValue::GetEnumTableIndex(const EnumTable* aTable) {
1193 int16_t index = sEnumTableArray->IndexOf(aTable);
1194 if (index < 0) {
1195 index = sEnumTableArray->Length();
1196 NS_ASSERTION(index <= NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE,
1197 "too many enum tables");
1198 sEnumTableArray->AppendElement(aTable);
1201 return index;
1204 int32_t nsAttrValue::EnumTableEntryToValue(const EnumTable* aEnumTable,
1205 const EnumTable* aTableEntry) {
1206 int16_t index = GetEnumTableIndex(aEnumTable);
1207 int32_t value =
1208 (aTableEntry->value << NS_ATTRVALUE_ENUMTABLEINDEX_BITS) + index;
1209 return value;
1212 bool nsAttrValue::ParseEnumValue(const nsAString& aValue,
1213 const EnumTable* aTable, bool aCaseSensitive,
1214 const EnumTable* aDefaultValue) {
1215 ResetIfSet();
1216 const EnumTable* tableEntry = aTable;
1218 while (tableEntry->tag) {
1219 if (aCaseSensitive ? aValue.EqualsASCII(tableEntry->tag)
1220 : aValue.LowerCaseEqualsASCII(tableEntry->tag)) {
1221 int32_t value = EnumTableEntryToValue(aTable, tableEntry);
1223 bool equals = aCaseSensitive || aValue.EqualsASCII(tableEntry->tag);
1224 if (!equals) {
1225 nsAutoString tag;
1226 tag.AssignASCII(tableEntry->tag);
1227 nsContentUtils::ASCIIToUpper(tag);
1228 if ((equals = tag.Equals(aValue))) {
1229 value |= NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER;
1232 SetIntValueAndType(value, eEnum, equals ? nullptr : &aValue);
1233 NS_ASSERTION(GetEnumValue() == tableEntry->value,
1234 "failed to store enum properly");
1236 return true;
1238 tableEntry++;
1241 if (aDefaultValue) {
1242 MOZ_ASSERT(aTable <= aDefaultValue && aDefaultValue < tableEntry,
1243 "aDefaultValue not inside aTable?");
1244 SetIntValueAndType(EnumTableEntryToValue(aTable, aDefaultValue), eEnum,
1245 &aValue);
1246 return true;
1249 return false;
1252 bool nsAttrValue::ParseSpecialIntValue(const nsAString& aString) {
1253 ResetIfSet();
1255 nsAutoString tmp(aString);
1256 nsContentUtils::ParseHTMLIntegerResultFlags result;
1257 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1259 if (result & nsContentUtils::eParseHTMLInteger_Error) {
1260 return false;
1263 bool isPercent = result & nsContentUtils::eParseHTMLInteger_IsPercent;
1264 int32_t val = std::max(originalVal, 0);
1265 bool nonStrict =
1266 val != originalVal ||
1267 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1268 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1270 // % (percent)
1271 if (isPercent || tmp.RFindChar('%') >= 0) {
1272 isPercent = true;
1275 SetIntValueAndType(val, isPercent ? ePercent : eInteger,
1276 nonStrict ? &aString : nullptr);
1277 return true;
1280 bool nsAttrValue::ParseIntWithBounds(const nsAString& aString, int32_t aMin,
1281 int32_t aMax) {
1282 MOZ_ASSERT(aMin < aMax, "bad boundaries");
1284 ResetIfSet();
1286 nsContentUtils::ParseHTMLIntegerResultFlags result;
1287 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1288 if (result & nsContentUtils::eParseHTMLInteger_Error) {
1289 return false;
1292 int32_t val = std::max(originalVal, aMin);
1293 val = std::min(val, aMax);
1294 bool nonStrict =
1295 (val != originalVal) ||
1296 (result & nsContentUtils::eParseHTMLInteger_IsPercent) ||
1297 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1298 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1300 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1302 return true;
1305 void nsAttrValue::ParseIntWithFallback(const nsAString& aString,
1306 int32_t aDefault, int32_t aMax) {
1307 ResetIfSet();
1309 nsContentUtils::ParseHTMLIntegerResultFlags result;
1310 int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
1311 bool nonStrict = false;
1312 if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 1) {
1313 val = aDefault;
1314 nonStrict = true;
1317 if (val > aMax) {
1318 val = aMax;
1319 nonStrict = true;
1322 if ((result & nsContentUtils::eParseHTMLInteger_IsPercent) ||
1323 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1324 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput)) {
1325 nonStrict = true;
1328 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1331 void nsAttrValue::ParseClampedNonNegativeInt(const nsAString& aString,
1332 int32_t aDefault, int32_t aMin,
1333 int32_t aMax) {
1334 ResetIfSet();
1336 nsContentUtils::ParseHTMLIntegerResultFlags result;
1337 int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
1338 bool nonStrict =
1339 (result & nsContentUtils::eParseHTMLInteger_IsPercent) ||
1340 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1341 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1343 if (result & nsContentUtils::eParseHTMLInteger_ErrorOverflow) {
1344 if (result & nsContentUtils::eParseHTMLInteger_Negative) {
1345 val = aDefault;
1346 } else {
1347 val = aMax;
1349 nonStrict = true;
1350 } else if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 0) {
1351 val = aDefault;
1352 nonStrict = true;
1353 } else if (val < aMin) {
1354 val = aMin;
1355 nonStrict = true;
1356 } else if (val > aMax) {
1357 val = aMax;
1358 nonStrict = true;
1361 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1364 bool nsAttrValue::ParseNonNegativeIntValue(const nsAString& aString) {
1365 ResetIfSet();
1367 nsContentUtils::ParseHTMLIntegerResultFlags result;
1368 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1369 if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal < 0) {
1370 return false;
1373 bool nonStrict =
1374 (result & nsContentUtils::eParseHTMLInteger_IsPercent) ||
1375 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1376 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1378 SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);
1380 return true;
1383 bool nsAttrValue::ParsePositiveIntValue(const nsAString& aString) {
1384 ResetIfSet();
1386 nsContentUtils::ParseHTMLIntegerResultFlags result;
1387 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1388 if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal <= 0) {
1389 return false;
1392 bool nonStrict =
1393 (result & nsContentUtils::eParseHTMLInteger_IsPercent) ||
1394 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1395 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1397 SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);
1399 return true;
1402 void nsAttrValue::SetColorValue(nscolor aColor, const nsAString& aString) {
1403 nsStringBuffer* buf = GetStringBuffer(aString).take();
1404 if (!buf) {
1405 return;
1408 MiscContainer* cont = EnsureEmptyMiscContainer();
1409 cont->mValue.mColor = aColor;
1410 cont->mType = eColor;
1412 // Save the literal string we were passed for round-tripping.
1413 cont->SetStringBitsMainThread(reinterpret_cast<uintptr_t>(buf) | eStringBase);
1416 bool nsAttrValue::ParseColor(const nsAString& aString) {
1417 ResetIfSet();
1419 // FIXME (partially, at least): HTML5's algorithm says we shouldn't do
1420 // the whitespace compression, trimming, or the test for emptiness.
1421 // (I'm a little skeptical that we shouldn't do the whitespace
1422 // trimming; WebKit also does it.)
1423 nsAutoString colorStr(aString);
1424 colorStr.CompressWhitespace(true, true);
1425 if (colorStr.IsEmpty()) {
1426 return false;
1429 nscolor color;
1430 // No color names begin with a '#'; in standards mode, all acceptable
1431 // numeric colors do.
1432 if (colorStr.First() == '#') {
1433 nsDependentString withoutHash(colorStr.get() + 1, colorStr.Length() - 1);
1434 if (NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
1435 SetColorValue(color, aString);
1436 return true;
1438 } else {
1439 if (NS_ColorNameToRGB(colorStr, &color)) {
1440 SetColorValue(color, aString);
1441 return true;
1445 // FIXME (maybe): HTML5 says we should handle system colors. This
1446 // means we probably need another storage type, since we'd need to
1447 // handle dynamic changes. However, I think this is a bad idea:
1448 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-May/026449.html
1450 // Use NS_LooseHexToRGB as a fallback if nothing above worked.
1451 if (NS_LooseHexToRGB(colorStr, &color)) {
1452 SetColorValue(color, aString);
1453 return true;
1456 return false;
1459 bool nsAttrValue::ParseDoubleValue(const nsAString& aString) {
1460 ResetIfSet();
1462 nsresult ec;
1463 double val = PromiseFlatString(aString).ToDouble(&ec);
1464 if (NS_FAILED(ec)) {
1465 return false;
1468 MiscContainer* cont = EnsureEmptyMiscContainer();
1469 cont->mDoubleValue = val;
1470 cont->mType = eDoubleValue;
1471 nsAutoString serializedFloat;
1472 serializedFloat.AppendFloat(val);
1473 SetMiscAtomOrString(serializedFloat.Equals(aString) ? nullptr : &aString);
1474 return true;
1477 bool nsAttrValue::ParseIntMarginValue(const nsAString& aString) {
1478 ResetIfSet();
1480 nsIntMargin margins;
1481 if (!nsContentUtils::ParseIntMarginValue(aString, margins)) return false;
1483 MiscContainer* cont = EnsureEmptyMiscContainer();
1484 cont->mValue.mIntMargin = new nsIntMargin(margins);
1485 cont->mType = eIntMarginValue;
1486 SetMiscAtomOrString(&aString);
1487 return true;
1490 bool nsAttrValue::ParseStyleAttribute(const nsAString& aString,
1491 nsIPrincipal* aMaybeScriptedPrincipal,
1492 nsStyledElement* aElement) {
1493 dom::Document* ownerDoc = aElement->OwnerDoc();
1494 nsHTMLCSSStyleSheet* sheet = ownerDoc->GetInlineStyleSheet();
1495 nsIURI* baseURI = aElement->GetBaseURIForStyleAttr();
1496 nsIURI* docURI = ownerDoc->GetDocumentURI();
1498 NS_ASSERTION(aElement->NodePrincipal() == ownerDoc->NodePrincipal(),
1499 "This is unexpected");
1501 nsIPrincipal* principal = aMaybeScriptedPrincipal ? aMaybeScriptedPrincipal
1502 : aElement->NodePrincipal();
1504 // If the (immutable) document URI does not match the element's base URI
1505 // (the common case is that they do match) do not cache the rule. This is
1506 // because the results of the CSS parser are dependent on these URIs, and we
1507 // do not want to have to account for the URIs in the hash lookup.
1508 // Similarly, if the triggering principal does not match the node principal,
1509 // do not cache the rule, since the principal will be encoded in any parsed
1510 // URLs in the rule.
1511 const bool cachingAllowed =
1512 sheet && baseURI == docURI && principal == aElement->NodePrincipal();
1513 if (cachingAllowed) {
1514 MiscContainer* cont = sheet->LookupStyleAttr(aString);
1515 if (cont) {
1516 // Set our MiscContainer to the cached one.
1517 NS_ADDREF(cont);
1518 SetPtrValueAndType(cont, eOtherBase);
1519 return true;
1523 RefPtr<URLExtraData> data = new URLExtraData(baseURI, docURI, principal,
1524 ownerDoc->GetReferrerPolicy());
1525 RefPtr<DeclarationBlock> decl = DeclarationBlock::FromCssText(
1526 aString, data, ownerDoc->GetCompatibilityMode(), ownerDoc->CSSLoader());
1527 if (!decl) {
1528 return false;
1530 decl->SetHTMLCSSStyleSheet(sheet);
1531 SetTo(decl.forget(), &aString);
1533 if (cachingAllowed) {
1534 MiscContainer* cont = GetMiscContainer();
1535 cont->Cache();
1538 return true;
1541 void nsAttrValue::SetMiscAtomOrString(const nsAString* aValue) {
1542 NS_ASSERTION(GetMiscContainer(), "Must have MiscContainer!");
1543 NS_ASSERTION(!GetMiscContainer()->mStringBits || IsInServoTraversal(),
1544 "Trying to re-set atom or string!");
1545 if (aValue) {
1546 uint32_t len = aValue->Length();
1547 // * We're allowing eCSSDeclaration attributes to store empty
1548 // strings as it can be beneficial to store an empty style
1549 // attribute as a parsed rule.
1550 // * We're allowing enumerated values because sometimes the empty
1551 // string corresponds to a particular enumerated value, especially
1552 // for enumerated values that are not limited enumerated.
1553 // Add other types as needed.
1554 NS_ASSERTION(len || Type() == eCSSDeclaration || Type() == eEnum,
1555 "Empty string?");
1556 MiscContainer* cont = GetMiscContainer();
1558 if (len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) {
1559 nsAtom* atom = MOZ_LIKELY(!IsInServoTraversal())
1560 ? NS_AtomizeMainThread(*aValue).take()
1561 : NS_Atomize(*aValue).take();
1562 NS_ENSURE_TRUE_VOID(atom);
1563 uintptr_t bits = reinterpret_cast<uintptr_t>(atom) | eAtomBase;
1565 // In the common case we're not in the servo traversal, and we can just
1566 // set the bits normally. The parallel case requires more care.
1567 if (MOZ_LIKELY(!IsInServoTraversal())) {
1568 cont->SetStringBitsMainThread(bits);
1569 } else if (!cont->mStringBits.compareExchange(0, bits)) {
1570 // We raced with somebody else setting the bits. Release our copy.
1571 atom->Release();
1573 } else {
1574 nsStringBuffer* buffer = GetStringBuffer(*aValue).take();
1575 NS_ENSURE_TRUE_VOID(buffer);
1576 uintptr_t bits = reinterpret_cast<uintptr_t>(buffer) | eStringBase;
1578 // In the common case we're not in the servo traversal, and we can just
1579 // set the bits normally. The parallel case requires more care.
1580 if (MOZ_LIKELY(!IsInServoTraversal())) {
1581 cont->SetStringBitsMainThread(bits);
1582 } else if (!cont->mStringBits.compareExchange(0, bits)) {
1583 // We raced with somebody else setting the bits. Release our copy.
1584 buffer->Release();
1590 void nsAttrValue::ResetMiscAtomOrString() {
1591 MiscContainer* cont = GetMiscContainer();
1592 void* ptr = MISC_STR_PTR(cont);
1593 if (ptr) {
1594 if (static_cast<ValueBaseType>(cont->mStringBits &
1595 NS_ATTRVALUE_BASETYPE_MASK) == eStringBase) {
1596 static_cast<nsStringBuffer*>(ptr)->Release();
1597 } else {
1598 static_cast<nsAtom*>(ptr)->Release();
1600 cont->SetStringBitsMainThread(0);
1604 void nsAttrValue::SetSVGType(ValueType aType, const void* aValue,
1605 const nsAString* aSerialized) {
1606 MOZ_ASSERT(IsSVGType(aType), "Not an SVG type");
1608 MiscContainer* cont = EnsureEmptyMiscContainer();
1609 // All SVG types are just pointers to classes so just setting any of them
1610 // will do. We'll lose type-safety but the signature of the calling
1611 // function should ensure we don't get anything unexpected, and once we
1612 // stick aValue in a union we lose type information anyway.
1613 cont->mValue.mSVGLength = static_cast<const SVGAnimatedLength*>(aValue);
1614 cont->mType = aType;
1615 SetMiscAtomOrString(aSerialized);
1618 MiscContainer* nsAttrValue::ClearMiscContainer() {
1619 MiscContainer* cont = nullptr;
1620 if (BaseType() == eOtherBase) {
1621 cont = GetMiscContainer();
1622 if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) {
1623 // This MiscContainer is shared, we need a new one.
1624 NS_RELEASE(cont);
1626 cont = AllocMiscContainer();
1627 SetPtrValueAndType(cont, eOtherBase);
1628 } else {
1629 switch (cont->mType) {
1630 case eCSSDeclaration: {
1631 MOZ_ASSERT(cont->mValue.mRefCount == 1);
1632 cont->Release();
1633 cont->Evict();
1634 NS_RELEASE(cont->mValue.mCSSDeclaration);
1635 break;
1637 case eURL: {
1638 NS_RELEASE(cont->mValue.mURL);
1639 break;
1641 case eAtomArray: {
1642 delete cont->mValue.mAtomArray;
1643 break;
1645 case eIntMarginValue: {
1646 delete cont->mValue.mIntMargin;
1647 break;
1649 default: {
1650 break;
1654 ResetMiscAtomOrString();
1655 } else {
1656 ResetIfSet();
1659 return cont;
1662 MiscContainer* nsAttrValue::EnsureEmptyMiscContainer() {
1663 MiscContainer* cont = ClearMiscContainer();
1664 if (cont) {
1665 MOZ_ASSERT(BaseType() == eOtherBase);
1666 ResetMiscAtomOrString();
1667 cont = GetMiscContainer();
1668 } else {
1669 cont = AllocMiscContainer();
1670 SetPtrValueAndType(cont, eOtherBase);
1673 return cont;
1676 bool nsAttrValue::EnsureEmptyAtomArray() {
1677 if (Type() == eAtomArray) {
1678 ResetMiscAtomOrString();
1679 GetAtomArrayValue()->Clear();
1680 return true;
1683 MiscContainer* cont = EnsureEmptyMiscContainer();
1684 cont->mValue.mAtomArray = new AtomArray;
1685 cont->mType = eAtomArray;
1687 return true;
1690 already_AddRefed<nsStringBuffer> nsAttrValue::GetStringBuffer(
1691 const nsAString& aValue) const {
1692 uint32_t len = aValue.Length();
1693 if (!len) {
1694 return nullptr;
1697 RefPtr<nsStringBuffer> buf = nsStringBuffer::FromString(aValue);
1698 if (buf && (buf->StorageSize() / sizeof(char16_t) - 1) == len) {
1699 return buf.forget();
1702 buf = nsStringBuffer::Alloc((len + 1) * sizeof(char16_t));
1703 if (!buf) {
1704 return nullptr;
1706 char16_t* data = static_cast<char16_t*>(buf->Data());
1707 CopyUnicodeTo(aValue, 0, data, len);
1708 data[len] = char16_t(0);
1709 return buf.forget();
1712 size_t nsAttrValue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
1713 size_t n = 0;
1715 switch (BaseType()) {
1716 case eStringBase: {
1717 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
1718 n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0;
1719 break;
1721 case eOtherBase: {
1722 MiscContainer* container = GetMiscContainer();
1723 if (!container) {
1724 break;
1726 if (container->IsRefCounted() && container->mValue.mRefCount > 1) {
1727 // We don't report this MiscContainer at all in order to avoid
1728 // twice-reporting it.
1729 // TODO DMD, bug 1027551 - figure out how to report this ref-counted
1730 // object just once.
1731 break;
1733 n += aMallocSizeOf(container);
1735 void* otherPtr = MISC_STR_PTR(container);
1736 // We only count the size of the object pointed by otherPtr if it's a
1737 // string. When it's an atom, it's counted separatly.
1738 if (otherPtr && static_cast<ValueBaseType>(container->mStringBits &
1739 NS_ATTRVALUE_BASETYPE_MASK) ==
1740 eStringBase) {
1741 nsStringBuffer* str = static_cast<nsStringBuffer*>(otherPtr);
1742 n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0;
1745 if (Type() == eCSSDeclaration && container->mValue.mCSSDeclaration) {
1746 // TODO: mCSSDeclaration might be owned by another object which
1747 // would make us count them twice, bug 677493.
1748 // Bug 1281964: For DeclarationBlock if we do measure we'll
1749 // need a way to call the Servo heap_size_of function.
1750 // n += container->mCSSDeclaration->SizeOfIncludingThis(aMallocSizeOf);
1751 } else if (Type() == eAtomArray && container->mValue.mAtomArray) {
1752 // Don't measure each nsAtom, they are measured separatly.
1753 n += container->mValue.mAtomArray->ShallowSizeOfIncludingThis(
1754 aMallocSizeOf);
1756 break;
1758 case eAtomBase: // Atoms are counted separately.
1759 case eIntegerBase: // The value is in mBits, nothing to do.
1760 break;
1763 return n;