Bug 1686668 [wpt PR 27185] - Update wpt metadata, a=testonly
[gecko.git] / dom / base / nsAttrValue.cpp
blobe0b059c4adfa58166309e6bb899412ca9cfba372
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/ShadowParts.h"
24 #include "mozilla/SVGAttrValueWrapper.h"
25 #include "mozilla/DeclarationBlock.h"
26 #include "mozilla/dom/CSSRuleBinding.h"
27 #include "nsContentUtils.h"
28 #include "nsReadableUtils.h"
29 #include "nsHTMLCSSStyleSheet.h"
30 #include "nsStyledElement.h"
31 #include "nsIURI.h"
32 #include "mozilla/dom/Document.h"
33 #include "ReferrerInfo.h"
34 #include <algorithm>
36 using namespace mozilla;
38 #define MISC_STR_PTR(_cont) \
39 reinterpret_cast<void*>((_cont)->mStringBits & NS_ATTRVALUE_POINTERVALUE_MASK)
41 /* static */
42 MiscContainer* nsAttrValue::AllocMiscContainer() {
43 MOZ_ASSERT(NS_IsMainThread());
44 MiscContainer* cont = nullptr;
45 std::swap(cont, sMiscContainerCache);
47 if (cont) {
48 return new (cont) MiscContainer;
51 return new MiscContainer;
54 /* static */
55 void nsAttrValue::DeallocMiscContainer(MiscContainer* aCont) {
56 MOZ_ASSERT(NS_IsMainThread());
57 if (!aCont) {
58 return;
61 if (!sMiscContainerCache) {
62 aCont->~MiscContainer();
63 sMiscContainerCache = aCont;
64 } else {
65 delete aCont;
69 bool MiscContainer::GetString(nsAString& aString) const {
70 void* ptr = MISC_STR_PTR(this);
72 if (!ptr) {
73 return false;
76 if (static_cast<nsAttrValue::ValueBaseType>(mStringBits &
77 NS_ATTRVALUE_BASETYPE_MASK) ==
78 nsAttrValue::eStringBase) {
79 nsStringBuffer* buffer = static_cast<nsStringBuffer*>(ptr);
80 if (!buffer) {
81 return false;
84 buffer->ToString(buffer->StorageSize() / sizeof(char16_t) - 1, aString);
85 return true;
88 nsAtom* atom = static_cast<nsAtom*>(ptr);
89 if (!atom) {
90 return false;
93 atom->ToString(aString);
94 return true;
97 void MiscContainer::Cache() {
98 // Not implemented for anything else yet.
99 if (mType != nsAttrValue::eCSSDeclaration) {
100 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
101 return;
104 MOZ_ASSERT(IsRefCounted());
105 MOZ_ASSERT(mValue.mRefCount > 0);
106 MOZ_ASSERT(!mValue.mCached);
108 nsHTMLCSSStyleSheet* sheet = mValue.mCSSDeclaration->GetHTMLCSSStyleSheet();
109 if (!sheet) {
110 return;
113 nsString str;
114 bool gotString = GetString(str);
115 if (!gotString) {
116 return;
119 sheet->CacheStyleAttr(str, this);
120 mValue.mCached = 1;
122 // This has to be immutable once it goes into the cache.
123 mValue.mCSSDeclaration->SetImmutable();
126 void MiscContainer::Evict() {
127 // Not implemented for anything else yet.
128 if (mType != nsAttrValue::eCSSDeclaration) {
129 MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type");
130 return;
132 MOZ_ASSERT(IsRefCounted());
133 MOZ_ASSERT(mValue.mRefCount == 0);
135 if (!mValue.mCached) {
136 return;
139 nsHTMLCSSStyleSheet* sheet = mValue.mCSSDeclaration->GetHTMLCSSStyleSheet();
140 MOZ_ASSERT(sheet);
142 nsString str;
143 DebugOnly<bool> gotString = GetString(str);
144 MOZ_ASSERT(gotString);
146 sheet->EvictStyleAttr(str, this);
147 mValue.mCached = 0;
150 nsTArray<const nsAttrValue::EnumTable*>* nsAttrValue::sEnumTableArray = nullptr;
151 MiscContainer* nsAttrValue::sMiscContainerCache = nullptr;
153 nsAttrValue::nsAttrValue() : mBits(0) {}
155 nsAttrValue::nsAttrValue(const nsAttrValue& aOther) : mBits(0) {
156 SetTo(aOther);
159 nsAttrValue::nsAttrValue(const nsAString& aValue) : mBits(0) { SetTo(aValue); }
161 nsAttrValue::nsAttrValue(nsAtom* aValue) : mBits(0) { SetTo(aValue); }
163 nsAttrValue::nsAttrValue(already_AddRefed<DeclarationBlock> aValue,
164 const nsAString* aSerialized)
165 : mBits(0) {
166 SetTo(std::move(aValue), aSerialized);
169 nsAttrValue::nsAttrValue(const nsIntMargin& aValue) : mBits(0) {
170 SetTo(aValue);
173 nsAttrValue::~nsAttrValue() { ResetIfSet(); }
175 /* static */
176 void nsAttrValue::Init() {
177 MOZ_ASSERT(!sEnumTableArray, "nsAttrValue already initialized");
178 sEnumTableArray = new nsTArray<const EnumTable*>;
181 /* static */
182 void nsAttrValue::Shutdown() {
183 MOZ_ASSERT(NS_IsMainThread());
184 delete sEnumTableArray;
185 sEnumTableArray = nullptr;
186 // The MiscContainer pointed to by sMiscContainerCache has already
187 // be destructed so `delete sMiscContainerCache` is
188 // dangerous. Invoke `operator delete` to free the memory.
189 ::operator delete(sMiscContainerCache);
190 sMiscContainerCache = nullptr;
193 void nsAttrValue::Reset() {
194 switch (BaseType()) {
195 case eStringBase: {
196 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
197 if (str) {
198 str->Release();
201 break;
203 case eOtherBase: {
204 MiscContainer* cont = GetMiscContainer();
205 if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) {
206 NS_RELEASE(cont);
207 break;
210 DeallocMiscContainer(ClearMiscContainer());
212 break;
214 case eAtomBase: {
215 nsAtom* atom = GetAtomValue();
216 NS_RELEASE(atom);
218 break;
220 case eIntegerBase: {
221 break;
225 mBits = 0;
228 void nsAttrValue::SetTo(const nsAttrValue& aOther) {
229 if (this == &aOther) {
230 return;
233 switch (aOther.BaseType()) {
234 case eStringBase: {
235 ResetIfSet();
236 nsStringBuffer* str = static_cast<nsStringBuffer*>(aOther.GetPtr());
237 if (str) {
238 str->AddRef();
239 SetPtrValueAndType(str, eStringBase);
241 return;
243 case eOtherBase: {
244 break;
246 case eAtomBase: {
247 ResetIfSet();
248 nsAtom* atom = aOther.GetAtomValue();
249 NS_ADDREF(atom);
250 SetPtrValueAndType(atom, eAtomBase);
251 return;
253 case eIntegerBase: {
254 ResetIfSet();
255 mBits = aOther.mBits;
256 return;
260 MiscContainer* otherCont = aOther.GetMiscContainer();
261 if (otherCont->IsRefCounted()) {
262 DeallocMiscContainer(ClearMiscContainer());
263 NS_ADDREF(otherCont);
264 SetPtrValueAndType(otherCont, eOtherBase);
265 return;
268 MiscContainer* cont = EnsureEmptyMiscContainer();
269 switch (otherCont->mType) {
270 case eInteger: {
271 cont->mValue.mInteger = otherCont->mValue.mInteger;
272 break;
274 case eEnum: {
275 cont->mValue.mEnumValue = otherCont->mValue.mEnumValue;
276 break;
278 case ePercent: {
279 cont->mDoubleValue = otherCont->mDoubleValue;
280 break;
282 case eColor: {
283 cont->mValue.mColor = otherCont->mValue.mColor;
284 break;
286 case eShadowParts:
287 case eCSSDeclaration: {
288 MOZ_CRASH("These should be refcounted!");
290 case eURL: {
291 NS_ADDREF(cont->mValue.mURL = otherCont->mValue.mURL);
292 break;
294 case eAtomArray: {
295 if (!EnsureEmptyAtomArray()) {
296 Reset();
297 return;
299 // XXX(Bug 1631371) Check if this should use a fallible operation as it
300 // pretended earlier.
301 GetAtomArrayValue()->AppendElements(*otherCont->mValue.mAtomArray);
302 break;
304 case eDoubleValue: {
305 cont->mDoubleValue = otherCont->mDoubleValue;
306 break;
308 case eIntMarginValue: {
309 if (otherCont->mValue.mIntMargin) {
310 cont->mValue.mIntMargin =
311 new nsIntMargin(*otherCont->mValue.mIntMargin);
313 break;
315 default: {
316 if (IsSVGType(otherCont->mType)) {
317 // All SVG types are just pointers to classes and will therefore have
318 // the same size so it doesn't really matter which one we assign
319 cont->mValue.mSVGLength = otherCont->mValue.mSVGLength;
320 } else {
321 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
323 break;
327 void* otherPtr = MISC_STR_PTR(otherCont);
328 if (otherPtr) {
329 if (static_cast<ValueBaseType>(otherCont->mStringBits &
330 NS_ATTRVALUE_BASETYPE_MASK) == eStringBase) {
331 static_cast<nsStringBuffer*>(otherPtr)->AddRef();
332 } else {
333 static_cast<nsAtom*>(otherPtr)->AddRef();
335 cont->SetStringBitsMainThread(otherCont->mStringBits);
337 // Note, set mType after switch-case, otherwise EnsureEmptyAtomArray doesn't
338 // work correctly.
339 cont->mType = otherCont->mType;
342 void nsAttrValue::SetTo(const nsAString& aValue) {
343 ResetIfSet();
344 nsStringBuffer* buf = GetStringBuffer(aValue).take();
345 if (buf) {
346 SetPtrValueAndType(buf, eStringBase);
350 void nsAttrValue::SetTo(nsAtom* aValue) {
351 ResetIfSet();
352 if (aValue) {
353 NS_ADDREF(aValue);
354 SetPtrValueAndType(aValue, eAtomBase);
358 void nsAttrValue::SetTo(int16_t aInt) {
359 ResetIfSet();
360 SetIntValueAndType(aInt, eInteger, nullptr);
363 void nsAttrValue::SetTo(int32_t aInt, const nsAString* aSerialized) {
364 ResetIfSet();
365 SetIntValueAndType(aInt, eInteger, aSerialized);
368 void nsAttrValue::SetTo(double aValue, const nsAString* aSerialized) {
369 MiscContainer* cont = EnsureEmptyMiscContainer();
370 cont->mDoubleValue = aValue;
371 cont->mType = eDoubleValue;
372 SetMiscAtomOrString(aSerialized);
375 void nsAttrValue::SetTo(already_AddRefed<DeclarationBlock> aValue,
376 const nsAString* aSerialized) {
377 MiscContainer* cont = EnsureEmptyMiscContainer();
378 MOZ_ASSERT(cont->mValue.mRefCount == 0);
379 cont->mValue.mCSSDeclaration = aValue.take();
380 cont->mType = eCSSDeclaration;
381 NS_ADDREF(cont);
382 SetMiscAtomOrString(aSerialized);
383 MOZ_ASSERT(cont->mValue.mRefCount == 1);
386 void nsAttrValue::SetTo(nsIURI* aValue, const nsAString* aSerialized) {
387 MiscContainer* cont = EnsureEmptyMiscContainer();
388 NS_ADDREF(cont->mValue.mURL = aValue);
389 cont->mType = eURL;
390 SetMiscAtomOrString(aSerialized);
393 void nsAttrValue::SetTo(const nsIntMargin& aValue) {
394 MiscContainer* cont = EnsureEmptyMiscContainer();
395 cont->mValue.mIntMargin = new nsIntMargin(aValue);
396 cont->mType = eIntMarginValue;
399 void nsAttrValue::SetToSerialized(const nsAttrValue& aOther) {
400 if (aOther.Type() != nsAttrValue::eString &&
401 aOther.Type() != nsAttrValue::eAtom) {
402 nsAutoString val;
403 aOther.ToString(val);
404 SetTo(val);
405 } else {
406 SetTo(aOther);
410 void nsAttrValue::SetTo(const SVGAnimatedOrient& aValue,
411 const nsAString* aSerialized) {
412 SetSVGType(eSVGOrient, &aValue, aSerialized);
415 void nsAttrValue::SetTo(const SVGAnimatedIntegerPair& aValue,
416 const nsAString* aSerialized) {
417 SetSVGType(eSVGIntegerPair, &aValue, aSerialized);
420 void nsAttrValue::SetTo(const SVGAnimatedLength& aValue,
421 const nsAString* aSerialized) {
422 SetSVGType(eSVGLength, &aValue, aSerialized);
425 void nsAttrValue::SetTo(const SVGLengthList& aValue,
426 const nsAString* aSerialized) {
427 // While an empty string will parse as a length list, there's no need to store
428 // it (and SetMiscAtomOrString will assert if we try)
429 if (aSerialized && aSerialized->IsEmpty()) {
430 aSerialized = nullptr;
432 SetSVGType(eSVGLengthList, &aValue, aSerialized);
435 void nsAttrValue::SetTo(const SVGNumberList& aValue,
436 const nsAString* aSerialized) {
437 // While an empty string will parse as a number list, there's no need to store
438 // it (and SetMiscAtomOrString will assert if we try)
439 if (aSerialized && aSerialized->IsEmpty()) {
440 aSerialized = nullptr;
442 SetSVGType(eSVGNumberList, &aValue, aSerialized);
445 void nsAttrValue::SetTo(const SVGAnimatedNumberPair& aValue,
446 const nsAString* aSerialized) {
447 SetSVGType(eSVGNumberPair, &aValue, aSerialized);
450 void nsAttrValue::SetTo(const SVGPathData& aValue,
451 const nsAString* aSerialized) {
452 // While an empty string will parse as path data, there's no need to store it
453 // (and SetMiscAtomOrString will assert if we try)
454 if (aSerialized && aSerialized->IsEmpty()) {
455 aSerialized = nullptr;
457 SetSVGType(eSVGPathData, &aValue, aSerialized);
460 void nsAttrValue::SetTo(const SVGPointList& aValue,
461 const nsAString* aSerialized) {
462 // While an empty string will parse as a point list, there's no need to store
463 // it (and SetMiscAtomOrString will assert if we try)
464 if (aSerialized && aSerialized->IsEmpty()) {
465 aSerialized = nullptr;
467 SetSVGType(eSVGPointList, &aValue, aSerialized);
470 void nsAttrValue::SetTo(const SVGAnimatedPreserveAspectRatio& aValue,
471 const nsAString* aSerialized) {
472 SetSVGType(eSVGPreserveAspectRatio, &aValue, aSerialized);
475 void nsAttrValue::SetTo(const SVGStringList& aValue,
476 const nsAString* aSerialized) {
477 // While an empty string will parse as a string list, there's no need to store
478 // it (and SetMiscAtomOrString will assert if we try)
479 if (aSerialized && aSerialized->IsEmpty()) {
480 aSerialized = nullptr;
482 SetSVGType(eSVGStringList, &aValue, aSerialized);
485 void nsAttrValue::SetTo(const SVGTransformList& aValue,
486 const nsAString* aSerialized) {
487 // While an empty string will parse as a transform list, there's no need to
488 // store it (and SetMiscAtomOrString will assert if we try)
489 if (aSerialized && aSerialized->IsEmpty()) {
490 aSerialized = nullptr;
492 SetSVGType(eSVGTransformList, &aValue, aSerialized);
495 void nsAttrValue::SetTo(const SVGAnimatedViewBox& aValue,
496 const nsAString* aSerialized) {
497 SetSVGType(eSVGViewBox, &aValue, aSerialized);
500 void nsAttrValue::SwapValueWith(nsAttrValue& aOther) {
501 uintptr_t tmp = aOther.mBits;
502 aOther.mBits = mBits;
503 mBits = tmp;
506 void nsAttrValue::ToString(nsAString& aResult) const {
507 MiscContainer* cont = nullptr;
508 if (BaseType() == eOtherBase) {
509 cont = GetMiscContainer();
511 if (cont->GetString(aResult)) {
512 return;
516 switch (Type()) {
517 case eString: {
518 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
519 if (str) {
520 str->ToString(str->StorageSize() / sizeof(char16_t) - 1, aResult);
521 } else {
522 aResult.Truncate();
524 break;
526 case eAtom: {
527 nsAtom* atom = static_cast<nsAtom*>(GetPtr());
528 atom->ToString(aResult);
530 break;
532 case eInteger: {
533 nsAutoString intStr;
534 intStr.AppendInt(GetIntegerValue());
535 aResult = intStr;
537 break;
539 #ifdef DEBUG
540 case eColor: {
541 MOZ_ASSERT_UNREACHABLE("color attribute without string data");
542 aResult.Truncate();
543 break;
545 #endif
546 case eEnum: {
547 GetEnumString(aResult, false);
548 break;
550 case ePercent: {
551 nsAutoString str;
552 if (cont) {
553 str.AppendFloat(cont->mDoubleValue);
554 } else {
555 str.AppendInt(GetIntInternal());
557 aResult = str + u"%"_ns;
559 break;
561 case eCSSDeclaration: {
562 aResult.Truncate();
563 MiscContainer* container = GetMiscContainer();
564 if (DeclarationBlock* decl = container->mValue.mCSSDeclaration) {
565 nsAutoCString result;
566 decl->ToString(result);
567 CopyUTF8toUTF16(result, aResult);
570 // This can be reached during parallel selector matching with attribute
571 // selectors on the style attribute. SetMiscAtomOrString handles this
572 // case, and as of this writing this is the only consumer that needs it.
573 const_cast<nsAttrValue*>(this)->SetMiscAtomOrString(&aResult);
575 break;
577 case eDoubleValue: {
578 aResult.Truncate();
579 aResult.AppendFloat(GetDoubleValue());
580 break;
582 case eSVGIntegerPair: {
583 SVGAttrValueWrapper::ToString(
584 GetMiscContainer()->mValue.mSVGAnimatedIntegerPair, aResult);
585 break;
587 case eSVGOrient: {
588 SVGAttrValueWrapper::ToString(
589 GetMiscContainer()->mValue.mSVGAnimatedOrient, aResult);
590 break;
592 case eSVGLength: {
593 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLength,
594 aResult);
595 break;
597 case eSVGLengthList: {
598 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLengthList,
599 aResult);
600 break;
602 case eSVGNumberList: {
603 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGNumberList,
604 aResult);
605 break;
607 case eSVGNumberPair: {
608 SVGAttrValueWrapper::ToString(
609 GetMiscContainer()->mValue.mSVGAnimatedNumberPair, aResult);
610 break;
612 case eSVGPathData: {
613 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPathData,
614 aResult);
615 break;
617 case eSVGPointList: {
618 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPointList,
619 aResult);
620 break;
622 case eSVGPreserveAspectRatio: {
623 SVGAttrValueWrapper::ToString(
624 GetMiscContainer()->mValue.mSVGAnimatedPreserveAspectRatio, aResult);
625 break;
627 case eSVGStringList: {
628 SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGStringList,
629 aResult);
630 break;
632 case eSVGTransformList: {
633 SVGAttrValueWrapper::ToString(
634 GetMiscContainer()->mValue.mSVGTransformList, aResult);
635 break;
637 case eSVGViewBox: {
638 SVGAttrValueWrapper::ToString(
639 GetMiscContainer()->mValue.mSVGAnimatedViewBox, aResult);
640 break;
642 default: {
643 aResult.Truncate();
644 break;
649 already_AddRefed<nsAtom> nsAttrValue::GetAsAtom() const {
650 switch (Type()) {
651 case eString:
652 return NS_AtomizeMainThread(GetStringValue());
654 case eAtom: {
655 RefPtr<nsAtom> atom = GetAtomValue();
656 return atom.forget();
659 default: {
660 nsAutoString val;
661 ToString(val);
662 return NS_AtomizeMainThread(val);
667 const nsCheapString nsAttrValue::GetStringValue() const {
668 MOZ_ASSERT(Type() == eString, "wrong type");
670 return nsCheapString(static_cast<nsStringBuffer*>(GetPtr()));
673 bool nsAttrValue::GetColorValue(nscolor& aColor) const {
674 if (Type() != eColor) {
675 // Unparseable value, treat as unset.
676 NS_ASSERTION(Type() == eString, "unexpected type for color-valued attr");
677 return false;
680 aColor = GetMiscContainer()->mValue.mColor;
681 return true;
684 void nsAttrValue::GetEnumString(nsAString& aResult, bool aRealTag) const {
685 MOZ_ASSERT(Type() == eEnum, "wrong type");
687 uint32_t allEnumBits = (BaseType() == eIntegerBase)
688 ? static_cast<uint32_t>(GetIntInternal())
689 : GetMiscContainer()->mValue.mEnumValue;
690 int16_t val = allEnumBits >> NS_ATTRVALUE_ENUMTABLEINDEX_BITS;
691 const EnumTable* table = sEnumTableArray->ElementAt(
692 allEnumBits & NS_ATTRVALUE_ENUMTABLEINDEX_MASK);
694 while (table->tag) {
695 if (table->value == val) {
696 aResult.AssignASCII(table->tag);
697 if (!aRealTag &&
698 allEnumBits & NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER) {
699 nsContentUtils::ASCIIToUpper(aResult);
701 return;
703 table++;
706 MOZ_ASSERT_UNREACHABLE("couldn't find value in EnumTable");
709 uint32_t nsAttrValue::GetAtomCount() const {
710 ValueType type = Type();
712 if (type == eAtom) {
713 return 1;
716 if (type == eAtomArray) {
717 return GetAtomArrayValue()->Length();
720 return 0;
723 nsAtom* nsAttrValue::AtomAt(int32_t aIndex) const {
724 MOZ_ASSERT(aIndex >= 0, "Index must not be negative");
725 MOZ_ASSERT(GetAtomCount() > uint32_t(aIndex), "aIndex out of range");
727 if (BaseType() == eAtomBase) {
728 return GetAtomValue();
731 NS_ASSERTION(Type() == eAtomArray, "GetAtomCount must be confused");
733 return GetAtomArrayValue()->ElementAt(aIndex);
736 uint32_t nsAttrValue::HashValue() const {
737 switch (BaseType()) {
738 case eStringBase: {
739 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
740 if (str) {
741 uint32_t len = str->StorageSize() / sizeof(char16_t) - 1;
742 return HashString(static_cast<char16_t*>(str->Data()), len);
745 return 0;
747 case eOtherBase: {
748 break;
750 case eAtomBase:
751 case eIntegerBase: {
752 // mBits and uint32_t might have different size. This should silence
753 // any warnings or compile-errors. This is what the implementation of
754 // NS_PTR_TO_INT32 does to take care of the same problem.
755 return mBits - 0;
759 MiscContainer* cont = GetMiscContainer();
760 if (static_cast<ValueBaseType>(cont->mStringBits &
761 NS_ATTRVALUE_BASETYPE_MASK) == eAtomBase) {
762 return cont->mStringBits - 0;
765 switch (cont->mType) {
766 case eInteger: {
767 return cont->mValue.mInteger;
769 case eEnum: {
770 return cont->mValue.mEnumValue;
772 case ePercent: {
773 return cont->mDoubleValue;
775 case eColor: {
776 return cont->mValue.mColor;
778 case eCSSDeclaration: {
779 return NS_PTR_TO_INT32(cont->mValue.mCSSDeclaration);
781 case eURL: {
782 nsString str;
783 ToString(str);
784 return HashString(str);
786 case eAtomArray: {
787 uint32_t hash = 0;
788 uint32_t count = cont->mValue.mAtomArray->Length();
789 for (RefPtr<nsAtom>*cur = cont->mValue.mAtomArray->Elements(),
790 *end = cur + count;
791 cur != end; ++cur) {
792 hash = AddToHash(hash, cur->get());
794 return hash;
796 case eDoubleValue: {
797 // XXX this is crappy, but oh well
798 return cont->mDoubleValue;
800 case eIntMarginValue: {
801 return NS_PTR_TO_INT32(cont->mValue.mIntMargin);
803 default: {
804 if (IsSVGType(cont->mType)) {
805 // All SVG types are just pointers to classes so we can treat them alike
806 return NS_PTR_TO_INT32(cont->mValue.mSVGLength);
808 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
809 return 0;
814 bool nsAttrValue::Equals(const nsAttrValue& aOther) const {
815 if (BaseType() != aOther.BaseType()) {
816 return false;
819 switch (BaseType()) {
820 case eStringBase: {
821 return GetStringValue().Equals(aOther.GetStringValue());
823 case eOtherBase: {
824 break;
826 case eAtomBase:
827 case eIntegerBase: {
828 return mBits == aOther.mBits;
832 MiscContainer* thisCont = GetMiscContainer();
833 MiscContainer* otherCont = aOther.GetMiscContainer();
834 if (thisCont == otherCont) {
835 return true;
838 if (thisCont->mType != otherCont->mType) {
839 return false;
842 bool needsStringComparison = false;
844 switch (thisCont->mType) {
845 case eInteger: {
846 if (thisCont->mValue.mInteger == otherCont->mValue.mInteger) {
847 needsStringComparison = true;
849 break;
851 case eEnum: {
852 if (thisCont->mValue.mEnumValue == otherCont->mValue.mEnumValue) {
853 needsStringComparison = true;
855 break;
857 case ePercent: {
858 if (thisCont->mDoubleValue == otherCont->mDoubleValue) {
859 needsStringComparison = true;
861 break;
863 case eColor: {
864 if (thisCont->mValue.mColor == otherCont->mValue.mColor) {
865 needsStringComparison = true;
867 break;
869 case eCSSDeclaration: {
870 return thisCont->mValue.mCSSDeclaration ==
871 otherCont->mValue.mCSSDeclaration;
873 case eURL: {
874 return thisCont->mValue.mURL == otherCont->mValue.mURL;
876 case eAtomArray: {
877 // For classlists we could be insensitive to order, however
878 // classlists are never mapped attributes so they are never compared.
880 if (!(*thisCont->mValue.mAtomArray == *otherCont->mValue.mAtomArray)) {
881 return false;
884 needsStringComparison = true;
885 break;
887 case eDoubleValue: {
888 return thisCont->mDoubleValue == otherCont->mDoubleValue;
890 case eIntMarginValue: {
891 return thisCont->mValue.mIntMargin == otherCont->mValue.mIntMargin;
893 default: {
894 if (IsSVGType(thisCont->mType)) {
895 // Currently this method is never called for nsAttrValue objects that
896 // point to SVG data types.
897 // If that changes then we probably want to add methods to the
898 // corresponding SVG types to compare their base values.
899 // As a shortcut, however, we can begin by comparing the pointers.
900 MOZ_ASSERT(false, "Comparing nsAttrValues that point to SVG data");
901 return false;
903 MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer");
904 return false;
907 if (needsStringComparison) {
908 if (thisCont->mStringBits == otherCont->mStringBits) {
909 return true;
911 if ((static_cast<ValueBaseType>(thisCont->mStringBits &
912 NS_ATTRVALUE_BASETYPE_MASK) ==
913 eStringBase) &&
914 (static_cast<ValueBaseType>(otherCont->mStringBits &
915 NS_ATTRVALUE_BASETYPE_MASK) ==
916 eStringBase)) {
917 return nsCheapString(reinterpret_cast<nsStringBuffer*>(
918 static_cast<uintptr_t>(thisCont->mStringBits)))
919 .Equals(nsCheapString(reinterpret_cast<nsStringBuffer*>(
920 static_cast<uintptr_t>(otherCont->mStringBits))));
923 return false;
926 bool nsAttrValue::Equals(const nsAString& aValue,
927 nsCaseTreatment aCaseSensitive) const {
928 switch (BaseType()) {
929 case eStringBase: {
930 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
931 if (str) {
932 nsDependentString dep(static_cast<char16_t*>(str->Data()),
933 str->StorageSize() / sizeof(char16_t) - 1);
934 return aCaseSensitive == eCaseMatters
935 ? aValue.Equals(dep)
936 : nsContentUtils::EqualsIgnoreASCIICase(aValue, dep);
938 return aValue.IsEmpty();
940 case eAtomBase:
941 if (aCaseSensitive == eCaseMatters) {
942 return static_cast<nsAtom*>(GetPtr())->Equals(aValue);
944 return nsContentUtils::EqualsIgnoreASCIICase(
945 nsDependentAtomString(static_cast<nsAtom*>(GetPtr())), aValue);
946 default:
947 break;
950 nsAutoString val;
951 ToString(val);
952 return aCaseSensitive == eCaseMatters
953 ? val.Equals(aValue)
954 : nsContentUtils::EqualsIgnoreASCIICase(val, aValue);
957 bool nsAttrValue::Equals(const nsAtom* aValue,
958 nsCaseTreatment aCaseSensitive) const {
959 if (aCaseSensitive != eCaseMatters) {
960 // Need a better way to handle this!
961 nsAutoString value;
962 aValue->ToString(value);
963 return Equals(value, aCaseSensitive);
966 switch (BaseType()) {
967 case eStringBase: {
968 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
969 if (str) {
970 nsDependentString dep(static_cast<char16_t*>(str->Data()),
971 str->StorageSize() / sizeof(char16_t) - 1);
972 return aValue->Equals(dep);
974 return aValue == nsGkAtoms::_empty;
976 case eAtomBase: {
977 return static_cast<nsAtom*>(GetPtr()) == aValue;
979 default:
980 break;
983 nsAutoString val;
984 ToString(val);
985 return aValue->Equals(val);
988 struct HasPrefixFn {
989 static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
990 const nsAString& aSearchValue,
991 nsCaseTreatment aCaseSensitive) {
992 if (aCaseSensitive == eCaseMatters) {
993 if (aSearchValue.Length() > aAttrLen) {
994 return false;
996 return !memcmp(aAttrValue, aSearchValue.BeginReading(),
997 aSearchValue.Length() * sizeof(char16_t));
999 return StringBeginsWith(nsDependentString(aAttrValue, aAttrLen),
1000 aSearchValue,
1001 nsASCIICaseInsensitiveStringComparator);
1005 struct HasSuffixFn {
1006 static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
1007 const nsAString& aSearchValue,
1008 nsCaseTreatment aCaseSensitive) {
1009 if (aCaseSensitive == eCaseMatters) {
1010 if (aSearchValue.Length() > aAttrLen) {
1011 return false;
1013 return !memcmp(aAttrValue + aAttrLen - aSearchValue.Length(),
1014 aSearchValue.BeginReading(),
1015 aSearchValue.Length() * sizeof(char16_t));
1017 return StringEndsWith(nsDependentString(aAttrValue, aAttrLen), aSearchValue,
1018 nsASCIICaseInsensitiveStringComparator);
1022 struct HasSubstringFn {
1023 static bool Check(const char16_t* aAttrValue, size_t aAttrLen,
1024 const nsAString& aSearchValue,
1025 nsCaseTreatment aCaseSensitive) {
1026 if (aCaseSensitive == eCaseMatters) {
1027 if (aSearchValue.IsEmpty()) {
1028 return true;
1030 const char16_t* end = aAttrValue + aAttrLen;
1031 return std::search(aAttrValue, end, aSearchValue.BeginReading(),
1032 aSearchValue.EndReading()) != end;
1034 return FindInReadable(aSearchValue, nsDependentString(aAttrValue, aAttrLen),
1035 nsASCIICaseInsensitiveStringComparator);
1039 template <typename F>
1040 bool nsAttrValue::SubstringCheck(const nsAString& aValue,
1041 nsCaseTreatment aCaseSensitive) const {
1042 switch (BaseType()) {
1043 case eStringBase: {
1044 auto str = static_cast<nsStringBuffer*>(GetPtr());
1045 if (str) {
1046 return F::Check(static_cast<char16_t*>(str->Data()),
1047 str->StorageSize() / sizeof(char16_t) - 1, aValue,
1048 aCaseSensitive);
1050 return aValue.IsEmpty();
1052 case eAtomBase: {
1053 auto atom = static_cast<nsAtom*>(GetPtr());
1054 return F::Check(atom->GetUTF16String(), atom->GetLength(), aValue,
1055 aCaseSensitive);
1057 default:
1058 break;
1061 nsAutoString val;
1062 ToString(val);
1063 return F::Check(val.BeginReading(), val.Length(), aValue, aCaseSensitive);
1066 bool nsAttrValue::HasPrefix(const nsAString& aValue,
1067 nsCaseTreatment aCaseSensitive) const {
1068 return SubstringCheck<HasPrefixFn>(aValue, aCaseSensitive);
1071 bool nsAttrValue::HasSuffix(const nsAString& aValue,
1072 nsCaseTreatment aCaseSensitive) const {
1073 return SubstringCheck<HasSuffixFn>(aValue, aCaseSensitive);
1076 bool nsAttrValue::HasSubstring(const nsAString& aValue,
1077 nsCaseTreatment aCaseSensitive) const {
1078 return SubstringCheck<HasSubstringFn>(aValue, aCaseSensitive);
1081 bool nsAttrValue::EqualsAsStrings(const nsAttrValue& aOther) const {
1082 if (Type() == aOther.Type()) {
1083 return Equals(aOther);
1086 // We need to serialize at least one nsAttrValue before passing to
1087 // Equals(const nsAString&), but we can avoid unnecessarily serializing both
1088 // by checking if one is already of a string type.
1089 bool thisIsString = (BaseType() == eStringBase || BaseType() == eAtomBase);
1090 const nsAttrValue& lhs = thisIsString ? *this : aOther;
1091 const nsAttrValue& rhs = thisIsString ? aOther : *this;
1093 switch (rhs.BaseType()) {
1094 case eAtomBase:
1095 return lhs.Equals(rhs.GetAtomValue(), eCaseMatters);
1097 case eStringBase:
1098 return lhs.Equals(rhs.GetStringValue(), eCaseMatters);
1100 default: {
1101 nsAutoString val;
1102 rhs.ToString(val);
1103 return lhs.Equals(val, eCaseMatters);
1108 bool nsAttrValue::Contains(nsAtom* aValue,
1109 nsCaseTreatment aCaseSensitive) const {
1110 switch (BaseType()) {
1111 case eAtomBase: {
1112 nsAtom* atom = GetAtomValue();
1113 if (aCaseSensitive == eCaseMatters) {
1114 return aValue == atom;
1117 // For performance reasons, don't do a full on unicode case insensitive
1118 // string comparison. This is only used for quirks mode anyway.
1119 return nsContentUtils::EqualsIgnoreASCIICase(aValue, atom);
1121 default: {
1122 if (Type() == eAtomArray) {
1123 AtomArray* array = GetAtomArrayValue();
1124 if (aCaseSensitive == eCaseMatters) {
1125 return array->Contains(aValue);
1128 for (RefPtr<nsAtom>& cur : *array) {
1129 // For performance reasons, don't do a full on unicode case
1130 // insensitive string comparison. This is only used for quirks mode
1131 // anyway.
1132 if (nsContentUtils::EqualsIgnoreASCIICase(aValue, cur)) {
1133 return true;
1140 return false;
1143 struct AtomArrayStringComparator {
1144 bool Equals(nsAtom* atom, const nsAString& string) const {
1145 return atom->Equals(string);
1149 bool nsAttrValue::Contains(const nsAString& aValue) const {
1150 switch (BaseType()) {
1151 case eAtomBase: {
1152 nsAtom* atom = GetAtomValue();
1153 return atom->Equals(aValue);
1155 default: {
1156 if (Type() == eAtomArray) {
1157 AtomArray* array = GetAtomArrayValue();
1158 return array->Contains(aValue, AtomArrayStringComparator());
1163 return false;
1166 void nsAttrValue::ParseAtom(const nsAString& aValue) {
1167 ResetIfSet();
1169 RefPtr<nsAtom> atom = NS_Atomize(aValue);
1170 if (atom) {
1171 SetPtrValueAndType(atom.forget().take(), eAtomBase);
1175 void nsAttrValue::ParseAtomArray(const nsAString& aValue) {
1176 nsAString::const_iterator iter, end;
1177 aValue.BeginReading(iter);
1178 aValue.EndReading(end);
1179 bool hasSpace = false;
1181 // skip initial whitespace
1182 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1183 hasSpace = true;
1184 ++iter;
1187 if (iter == end) {
1188 SetTo(aValue);
1189 return;
1192 nsAString::const_iterator start(iter);
1194 // get first - and often only - atom
1195 do {
1196 ++iter;
1197 } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));
1199 RefPtr<nsAtom> classAtom = NS_AtomizeMainThread(Substring(start, iter));
1200 if (!classAtom) {
1201 Reset();
1202 return;
1205 // skip whitespace
1206 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1207 hasSpace = true;
1208 ++iter;
1211 if (iter == end && !hasSpace) {
1212 // we only found one classname and there was no whitespace so
1213 // don't bother storing a list
1214 ResetIfSet();
1215 nsAtom* atom = nullptr;
1216 classAtom.swap(atom);
1217 SetPtrValueAndType(atom, eAtomBase);
1218 return;
1221 if (!EnsureEmptyAtomArray()) {
1222 return;
1225 AtomArray* array = GetAtomArrayValue();
1227 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1228 // pretended earlier.
1229 array->AppendElement(std::move(classAtom));
1231 // parse the rest of the classnames
1232 while (iter != end) {
1233 start = iter;
1235 do {
1236 ++iter;
1237 } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));
1239 classAtom = NS_AtomizeMainThread(Substring(start, iter));
1241 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1242 // pretended earlier.
1243 array->AppendElement(std::move(classAtom));
1245 // skip whitespace
1246 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1247 ++iter;
1251 SetMiscAtomOrString(&aValue);
1254 void nsAttrValue::ParseStringOrAtom(const nsAString& aValue) {
1255 uint32_t len = aValue.Length();
1256 // Don't bother with atoms if it's an empty string since
1257 // we can store those efficiently anyway.
1258 if (len && len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) {
1259 ParseAtom(aValue);
1260 } else {
1261 SetTo(aValue);
1265 void nsAttrValue::ParsePartMapping(const nsAString& aValue) {
1266 ResetIfSet();
1267 MiscContainer* cont = EnsureEmptyMiscContainer();
1269 cont->mType = eShadowParts;
1270 cont->mValue.mShadowParts = new ShadowParts(ShadowParts::Parse(aValue));
1271 NS_ADDREF(cont);
1272 SetMiscAtomOrString(&aValue);
1273 MOZ_ASSERT(cont->mValue.mRefCount == 1);
1276 void nsAttrValue::SetIntValueAndType(int32_t aValue, ValueType aType,
1277 const nsAString* aStringValue) {
1278 if (aStringValue || aValue > NS_ATTRVALUE_INTEGERTYPE_MAXVALUE ||
1279 aValue < NS_ATTRVALUE_INTEGERTYPE_MINVALUE) {
1280 MiscContainer* cont = EnsureEmptyMiscContainer();
1281 switch (aType) {
1282 case eInteger: {
1283 cont->mValue.mInteger = aValue;
1284 break;
1286 case ePercent: {
1287 cont->mDoubleValue = aValue;
1288 break;
1290 case eEnum: {
1291 cont->mValue.mEnumValue = aValue;
1292 break;
1294 default: {
1295 MOZ_ASSERT_UNREACHABLE("unknown integer type");
1296 break;
1299 cont->mType = aType;
1300 SetMiscAtomOrString(aStringValue);
1301 } else {
1302 NS_ASSERTION(!mBits, "Reset before calling SetIntValueAndType!");
1303 mBits = (aValue * NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER) | aType;
1307 void nsAttrValue::SetDoubleValueAndType(double aValue, ValueType aType,
1308 const nsAString* aStringValue) {
1309 MOZ_ASSERT(aType == eDoubleValue || aType == ePercent, "Unexpected type");
1310 MiscContainer* cont = EnsureEmptyMiscContainer();
1311 cont->mDoubleValue = aValue;
1312 cont->mType = aType;
1313 SetMiscAtomOrString(aStringValue);
1316 int16_t nsAttrValue::GetEnumTableIndex(const EnumTable* aTable) {
1317 int16_t index = sEnumTableArray->IndexOf(aTable);
1318 if (index < 0) {
1319 index = sEnumTableArray->Length();
1320 NS_ASSERTION(index <= NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE,
1321 "too many enum tables");
1322 sEnumTableArray->AppendElement(aTable);
1325 return index;
1328 int32_t nsAttrValue::EnumTableEntryToValue(const EnumTable* aEnumTable,
1329 const EnumTable* aTableEntry) {
1330 int16_t index = GetEnumTableIndex(aEnumTable);
1331 int32_t value =
1332 (aTableEntry->value << NS_ATTRVALUE_ENUMTABLEINDEX_BITS) + index;
1333 return value;
1336 bool nsAttrValue::ParseEnumValue(const nsAString& aValue,
1337 const EnumTable* aTable, bool aCaseSensitive,
1338 const EnumTable* aDefaultValue) {
1339 ResetIfSet();
1340 const EnumTable* tableEntry = aTable;
1342 while (tableEntry->tag) {
1343 if (aCaseSensitive ? aValue.EqualsASCII(tableEntry->tag)
1344 : aValue.LowerCaseEqualsASCII(tableEntry->tag)) {
1345 int32_t value = EnumTableEntryToValue(aTable, tableEntry);
1347 bool equals = aCaseSensitive || aValue.EqualsASCII(tableEntry->tag);
1348 if (!equals) {
1349 nsAutoString tag;
1350 tag.AssignASCII(tableEntry->tag);
1351 nsContentUtils::ASCIIToUpper(tag);
1352 if ((equals = tag.Equals(aValue))) {
1353 value |= NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER;
1356 SetIntValueAndType(value, eEnum, equals ? nullptr : &aValue);
1357 NS_ASSERTION(GetEnumValue() == tableEntry->value,
1358 "failed to store enum properly");
1360 return true;
1362 tableEntry++;
1365 if (aDefaultValue) {
1366 MOZ_ASSERT(aTable <= aDefaultValue && aDefaultValue < tableEntry,
1367 "aDefaultValue not inside aTable?");
1368 SetIntValueAndType(EnumTableEntryToValue(aTable, aDefaultValue), eEnum,
1369 &aValue);
1370 return true;
1373 return false;
1376 bool nsAttrValue::DoParseHTMLDimension(const nsAString& aInput,
1377 bool aEnsureNonzero) {
1378 ResetIfSet();
1380 // We don't use nsContentUtils::ParseHTMLInteger here because we
1381 // need a bunch of behavioral differences from it. We _could_ try to
1382 // use it, but it would not be a great fit.
1384 // https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values
1386 // Steps 1 and 2.
1387 const char16_t* position = aInput.BeginReading();
1388 const char16_t* end = aInput.EndReading();
1390 // We will need to keep track of whether this was a canonical representation
1391 // or not. It's non-canonical if it has leading whitespace, leading '+',
1392 // leading '0' characters, or trailing garbage.
1393 bool canonical = true;
1395 // Step 3.
1396 while (position != end && nsContentUtils::IsHTMLWhitespace(*position)) {
1397 canonical = false; // Leading whitespace
1398 ++position;
1401 // Step 4.
1402 if (position == end || *position < char16_t('0') ||
1403 *position > char16_t('9')) {
1404 return false;
1407 // Step 5.
1408 CheckedInt32 value = 0;
1410 // Collect up leading '0' first to avoid extra branching in the main
1411 // loop to set 'canonical' properly.
1412 while (position != end && *position == char16_t('0')) {
1413 canonical = false; // Leading '0'
1414 ++position;
1417 // Now collect up other digits.
1418 while (position != end && *position >= char16_t('0') &&
1419 *position <= char16_t('9')) {
1420 value = value * 10 + (*position - char16_t('0'));
1421 if (!value.isValid()) {
1422 // The spec assumes we can deal with arbitrary-size integers here, but we
1423 // really can't. If someone sets something too big, just bail out and
1424 // ignore it.
1425 return false;
1427 ++position;
1430 // Step 6 is implemented implicitly via the various "position != end" guards
1431 // from this point on.
1433 Maybe<double> doubleValue;
1434 // Step 7. The return in step 7.2 is handled by just falling through to the
1435 // code below this block when we reach end of input or a non-digit, because
1436 // the while loop will terminate at that point.
1437 if (position != end && *position == char16_t('.')) {
1438 canonical = false; // Let's not rely on double serialization reproducing
1439 // the string we started with.
1440 // Step 7.1.
1441 ++position;
1442 // If we have a '.' _not_ followed by digits, this is not as efficient as it
1443 // could be, because we will store as a double while we could have stored as
1444 // an int. But that seems like a pretty rare case.
1445 doubleValue.emplace(value.value());
1446 // Step 7.3.
1447 double divisor = 1.0f;
1448 // Step 7.4.
1449 while (position != end && *position >= char16_t('0') &&
1450 *position <= char16_t('9')) {
1451 // Step 7.4.1.
1452 divisor = divisor * 10.0f;
1453 // Step 7.4.2.
1454 doubleValue.ref() += (*position - char16_t('0')) / divisor;
1455 // Step 7.4.3.
1456 ++position;
1457 // Step 7.4.4 and 7.4.5 are captured in the while loop condition and the
1458 // "position != end" checks below.
1462 if (aEnsureNonzero && value.value() == 0 &&
1463 (!doubleValue || *doubleValue == 0.0f)) {
1464 // Not valid. Just drop it.
1465 return false;
1468 // Step 8 and the spec's early return from step 7.2.
1469 ValueType type;
1470 if (position != end && *position == char16_t('%')) {
1471 type = ePercent;
1472 ++position;
1473 } else if (doubleValue) {
1474 type = eDoubleValue;
1475 } else {
1476 type = eInteger;
1479 if (position != end) {
1480 canonical = false;
1483 if (doubleValue) {
1484 MOZ_ASSERT(!canonical, "We set it false above!");
1485 SetDoubleValueAndType(*doubleValue, type, &aInput);
1486 } else {
1487 SetIntValueAndType(value.value(), type, canonical ? nullptr : &aInput);
1490 #ifdef DEBUG
1491 nsAutoString str;
1492 ToString(str);
1493 MOZ_ASSERT(str == aInput, "We messed up our 'canonical' boolean!");
1494 #endif
1496 return true;
1499 bool nsAttrValue::ParseIntWithBounds(const nsAString& aString, int32_t aMin,
1500 int32_t aMax) {
1501 MOZ_ASSERT(aMin < aMax, "bad boundaries");
1503 ResetIfSet();
1505 nsContentUtils::ParseHTMLIntegerResultFlags result;
1506 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1507 if (result & nsContentUtils::eParseHTMLInteger_Error) {
1508 return false;
1511 int32_t val = std::max(originalVal, aMin);
1512 val = std::min(val, aMax);
1513 bool nonStrict =
1514 (val != originalVal) ||
1515 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1516 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1518 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1520 return true;
1523 void nsAttrValue::ParseIntWithFallback(const nsAString& aString,
1524 int32_t aDefault, int32_t aMax) {
1525 ResetIfSet();
1527 nsContentUtils::ParseHTMLIntegerResultFlags result;
1528 int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
1529 bool nonStrict = false;
1530 if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 1) {
1531 val = aDefault;
1532 nonStrict = true;
1535 if (val > aMax) {
1536 val = aMax;
1537 nonStrict = true;
1540 if ((result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1541 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput)) {
1542 nonStrict = true;
1545 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1548 void nsAttrValue::ParseClampedNonNegativeInt(const nsAString& aString,
1549 int32_t aDefault, int32_t aMin,
1550 int32_t aMax) {
1551 ResetIfSet();
1553 nsContentUtils::ParseHTMLIntegerResultFlags result;
1554 int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result);
1555 bool nonStrict =
1556 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1557 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1559 if (result & nsContentUtils::eParseHTMLInteger_ErrorOverflow) {
1560 if (result & nsContentUtils::eParseHTMLInteger_Negative) {
1561 val = aDefault;
1562 } else {
1563 val = aMax;
1565 nonStrict = true;
1566 } else if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 0) {
1567 val = aDefault;
1568 nonStrict = true;
1569 } else if (val < aMin) {
1570 val = aMin;
1571 nonStrict = true;
1572 } else if (val > aMax) {
1573 val = aMax;
1574 nonStrict = true;
1577 SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr);
1580 bool nsAttrValue::ParseNonNegativeIntValue(const nsAString& aString) {
1581 ResetIfSet();
1583 nsContentUtils::ParseHTMLIntegerResultFlags result;
1584 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1585 if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal < 0) {
1586 return false;
1589 bool nonStrict =
1590 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1591 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1593 SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);
1595 return true;
1598 bool nsAttrValue::ParsePositiveIntValue(const nsAString& aString) {
1599 ResetIfSet();
1601 nsContentUtils::ParseHTMLIntegerResultFlags result;
1602 int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
1603 if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal <= 0) {
1604 return false;
1607 bool nonStrict =
1608 (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
1609 (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
1611 SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr);
1613 return true;
1616 void nsAttrValue::SetColorValue(nscolor aColor, const nsAString& aString) {
1617 nsStringBuffer* buf = GetStringBuffer(aString).take();
1618 if (!buf) {
1619 return;
1622 MiscContainer* cont = EnsureEmptyMiscContainer();
1623 cont->mValue.mColor = aColor;
1624 cont->mType = eColor;
1626 // Save the literal string we were passed for round-tripping.
1627 cont->SetStringBitsMainThread(reinterpret_cast<uintptr_t>(buf) | eStringBase);
1630 bool nsAttrValue::ParseColor(const nsAString& aString) {
1631 ResetIfSet();
1633 // FIXME (partially, at least): HTML5's algorithm says we shouldn't do
1634 // the whitespace compression, trimming, or the test for emptiness.
1635 // (I'm a little skeptical that we shouldn't do the whitespace
1636 // trimming; WebKit also does it.)
1637 nsAutoString colorStr(aString);
1638 colorStr.CompressWhitespace(true, true);
1639 if (colorStr.IsEmpty()) {
1640 return false;
1643 nscolor color;
1644 // No color names begin with a '#'; in standards mode, all acceptable
1645 // numeric colors do.
1646 if (colorStr.First() == '#') {
1647 nsDependentString withoutHash(colorStr.get() + 1, colorStr.Length() - 1);
1648 if (NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
1649 SetColorValue(color, aString);
1650 return true;
1652 } else {
1653 if (NS_ColorNameToRGB(colorStr, &color)) {
1654 SetColorValue(color, aString);
1655 return true;
1659 // FIXME (maybe): HTML5 says we should handle system colors. This
1660 // means we probably need another storage type, since we'd need to
1661 // handle dynamic changes. However, I think this is a bad idea:
1662 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-May/026449.html
1664 // Use NS_LooseHexToRGB as a fallback if nothing above worked.
1665 if (NS_LooseHexToRGB(colorStr, &color)) {
1666 SetColorValue(color, aString);
1667 return true;
1670 return false;
1673 bool nsAttrValue::ParseDoubleValue(const nsAString& aString) {
1674 ResetIfSet();
1676 nsresult ec;
1677 double val = PromiseFlatString(aString).ToDouble(&ec);
1678 if (NS_FAILED(ec)) {
1679 return false;
1682 MiscContainer* cont = EnsureEmptyMiscContainer();
1683 cont->mDoubleValue = val;
1684 cont->mType = eDoubleValue;
1685 nsAutoString serializedFloat;
1686 serializedFloat.AppendFloat(val);
1687 SetMiscAtomOrString(serializedFloat.Equals(aString) ? nullptr : &aString);
1688 return true;
1691 bool nsAttrValue::ParseIntMarginValue(const nsAString& aString) {
1692 ResetIfSet();
1694 nsIntMargin margins;
1695 if (!nsContentUtils::ParseIntMarginValue(aString, margins)) return false;
1697 MiscContainer* cont = EnsureEmptyMiscContainer();
1698 cont->mValue.mIntMargin = new nsIntMargin(margins);
1699 cont->mType = eIntMarginValue;
1700 SetMiscAtomOrString(&aString);
1701 return true;
1704 bool nsAttrValue::ParseStyleAttribute(const nsAString& aString,
1705 nsIPrincipal* aMaybeScriptedPrincipal,
1706 nsStyledElement* aElement) {
1707 dom::Document* ownerDoc = aElement->OwnerDoc();
1708 nsHTMLCSSStyleSheet* sheet = ownerDoc->GetInlineStyleSheet();
1709 nsIURI* baseURI = aElement->GetBaseURIForStyleAttr();
1710 nsIURI* docURI = ownerDoc->GetDocumentURI();
1712 NS_ASSERTION(aElement->NodePrincipal() == ownerDoc->NodePrincipal(),
1713 "This is unexpected");
1715 nsIPrincipal* principal = aMaybeScriptedPrincipal ? aMaybeScriptedPrincipal
1716 : aElement->NodePrincipal();
1718 // If the (immutable) document URI does not match the element's base URI
1719 // (the common case is that they do match) do not cache the rule. This is
1720 // because the results of the CSS parser are dependent on these URIs, and we
1721 // do not want to have to account for the URIs in the hash lookup.
1722 // Similarly, if the triggering principal does not match the node principal,
1723 // do not cache the rule, since the principal will be encoded in any parsed
1724 // URLs in the rule.
1725 const bool cachingAllowed =
1726 sheet && baseURI == docURI && principal == aElement->NodePrincipal();
1727 if (cachingAllowed) {
1728 MiscContainer* cont = sheet->LookupStyleAttr(aString);
1729 if (cont) {
1730 // Set our MiscContainer to the cached one.
1731 NS_ADDREF(cont);
1732 SetPtrValueAndType(cont, eOtherBase);
1733 return true;
1737 nsCOMPtr<nsIReferrerInfo> referrerInfo =
1738 dom::ReferrerInfo::CreateForInternalCSSResources(ownerDoc);
1739 auto data = MakeRefPtr<URLExtraData>(baseURI, referrerInfo, principal);
1740 RefPtr<DeclarationBlock> decl = DeclarationBlock::FromCssText(
1741 aString, data, ownerDoc->GetCompatibilityMode(), ownerDoc->CSSLoader(),
1742 dom::CSSRule_Binding::STYLE_RULE);
1743 if (!decl) {
1744 return false;
1746 decl->SetHTMLCSSStyleSheet(sheet);
1747 SetTo(decl.forget(), &aString);
1749 if (cachingAllowed) {
1750 MiscContainer* cont = GetMiscContainer();
1751 cont->Cache();
1754 return true;
1757 void nsAttrValue::SetMiscAtomOrString(const nsAString* aValue) {
1758 NS_ASSERTION(GetMiscContainer(), "Must have MiscContainer!");
1759 NS_ASSERTION(!GetMiscContainer()->mStringBits || IsInServoTraversal(),
1760 "Trying to re-set atom or string!");
1761 if (aValue) {
1762 uint32_t len = aValue->Length();
1763 // * We're allowing eCSSDeclaration attributes to store empty
1764 // strings as it can be beneficial to store an empty style
1765 // attribute as a parsed rule.
1766 // * We're allowing enumerated values because sometimes the empty
1767 // string corresponds to a particular enumerated value, especially
1768 // for enumerated values that are not limited enumerated.
1769 // Add other types as needed.
1770 NS_ASSERTION(len || Type() == eCSSDeclaration || Type() == eEnum,
1771 "Empty string?");
1772 MiscContainer* cont = GetMiscContainer();
1774 if (len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) {
1775 nsAtom* atom = MOZ_LIKELY(!IsInServoTraversal())
1776 ? NS_AtomizeMainThread(*aValue).take()
1777 : NS_Atomize(*aValue).take();
1778 NS_ENSURE_TRUE_VOID(atom);
1779 uintptr_t bits = reinterpret_cast<uintptr_t>(atom) | eAtomBase;
1781 // In the common case we're not in the servo traversal, and we can just
1782 // set the bits normally. The parallel case requires more care.
1783 if (MOZ_LIKELY(!IsInServoTraversal())) {
1784 cont->SetStringBitsMainThread(bits);
1785 } else if (!cont->mStringBits.compareExchange(0, bits)) {
1786 // We raced with somebody else setting the bits. Release our copy.
1787 atom->Release();
1789 } else {
1790 nsStringBuffer* buffer = GetStringBuffer(*aValue).take();
1791 NS_ENSURE_TRUE_VOID(buffer);
1792 uintptr_t bits = reinterpret_cast<uintptr_t>(buffer) | eStringBase;
1794 // In the common case we're not in the servo traversal, and we can just
1795 // set the bits normally. The parallel case requires more care.
1796 if (MOZ_LIKELY(!IsInServoTraversal())) {
1797 cont->SetStringBitsMainThread(bits);
1798 } else if (!cont->mStringBits.compareExchange(0, bits)) {
1799 // We raced with somebody else setting the bits. Release our copy.
1800 buffer->Release();
1806 void nsAttrValue::ResetMiscAtomOrString() {
1807 MiscContainer* cont = GetMiscContainer();
1808 void* ptr = MISC_STR_PTR(cont);
1809 if (ptr) {
1810 if (static_cast<ValueBaseType>(cont->mStringBits &
1811 NS_ATTRVALUE_BASETYPE_MASK) == eStringBase) {
1812 static_cast<nsStringBuffer*>(ptr)->Release();
1813 } else {
1814 static_cast<nsAtom*>(ptr)->Release();
1816 cont->SetStringBitsMainThread(0);
1820 void nsAttrValue::SetSVGType(ValueType aType, const void* aValue,
1821 const nsAString* aSerialized) {
1822 MOZ_ASSERT(IsSVGType(aType), "Not an SVG type");
1824 MiscContainer* cont = EnsureEmptyMiscContainer();
1825 // All SVG types are just pointers to classes so just setting any of them
1826 // will do. We'll lose type-safety but the signature of the calling
1827 // function should ensure we don't get anything unexpected, and once we
1828 // stick aValue in a union we lose type information anyway.
1829 cont->mValue.mSVGLength = static_cast<const SVGAnimatedLength*>(aValue);
1830 cont->mType = aType;
1831 SetMiscAtomOrString(aSerialized);
1834 MiscContainer* nsAttrValue::ClearMiscContainer() {
1835 MiscContainer* cont = nullptr;
1836 if (BaseType() == eOtherBase) {
1837 cont = GetMiscContainer();
1838 if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) {
1839 // This MiscContainer is shared, we need a new one.
1840 NS_RELEASE(cont);
1842 cont = AllocMiscContainer();
1843 SetPtrValueAndType(cont, eOtherBase);
1844 } else {
1845 switch (cont->mType) {
1846 case eCSSDeclaration: {
1847 MOZ_ASSERT(cont->mValue.mRefCount == 1);
1848 cont->Release();
1849 cont->Evict();
1850 NS_RELEASE(cont->mValue.mCSSDeclaration);
1851 break;
1853 case eShadowParts: {
1854 MOZ_ASSERT(cont->mValue.mRefCount == 1);
1855 cont->Release();
1856 delete cont->mValue.mShadowParts;
1857 break;
1859 case eURL: {
1860 NS_RELEASE(cont->mValue.mURL);
1861 break;
1863 case eAtomArray: {
1864 delete cont->mValue.mAtomArray;
1865 break;
1867 case eIntMarginValue: {
1868 delete cont->mValue.mIntMargin;
1869 break;
1871 default: {
1872 break;
1876 ResetMiscAtomOrString();
1877 } else {
1878 ResetIfSet();
1881 return cont;
1884 MiscContainer* nsAttrValue::EnsureEmptyMiscContainer() {
1885 MiscContainer* cont = ClearMiscContainer();
1886 if (cont) {
1887 MOZ_ASSERT(BaseType() == eOtherBase);
1888 ResetMiscAtomOrString();
1889 cont = GetMiscContainer();
1890 } else {
1891 cont = AllocMiscContainer();
1892 SetPtrValueAndType(cont, eOtherBase);
1895 return cont;
1898 bool nsAttrValue::EnsureEmptyAtomArray() {
1899 if (Type() == eAtomArray) {
1900 ResetMiscAtomOrString();
1901 GetAtomArrayValue()->Clear();
1902 return true;
1905 MiscContainer* cont = EnsureEmptyMiscContainer();
1906 cont->mValue.mAtomArray = new AtomArray;
1907 cont->mType = eAtomArray;
1909 return true;
1912 already_AddRefed<nsStringBuffer> nsAttrValue::GetStringBuffer(
1913 const nsAString& aValue) const {
1914 uint32_t len = aValue.Length();
1915 if (!len) {
1916 return nullptr;
1919 RefPtr<nsStringBuffer> buf = nsStringBuffer::FromString(aValue);
1920 if (buf && (buf->StorageSize() / sizeof(char16_t) - 1) == len) {
1921 return buf.forget();
1924 buf = nsStringBuffer::Alloc((len + 1) * sizeof(char16_t));
1925 if (!buf) {
1926 return nullptr;
1928 char16_t* data = static_cast<char16_t*>(buf->Data());
1929 CopyUnicodeTo(aValue, 0, data, len);
1930 data[len] = char16_t(0);
1931 return buf.forget();
1934 size_t nsAttrValue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
1935 size_t n = 0;
1937 switch (BaseType()) {
1938 case eStringBase: {
1939 nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
1940 n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0;
1941 break;
1943 case eOtherBase: {
1944 MiscContainer* container = GetMiscContainer();
1945 if (!container) {
1946 break;
1948 if (container->IsRefCounted() && container->mValue.mRefCount > 1) {
1949 // We don't report this MiscContainer at all in order to avoid
1950 // twice-reporting it.
1951 // TODO DMD, bug 1027551 - figure out how to report this ref-counted
1952 // object just once.
1953 break;
1955 n += aMallocSizeOf(container);
1957 void* otherPtr = MISC_STR_PTR(container);
1958 // We only count the size of the object pointed by otherPtr if it's a
1959 // string. When it's an atom, it's counted separatly.
1960 if (otherPtr && static_cast<ValueBaseType>(container->mStringBits &
1961 NS_ATTRVALUE_BASETYPE_MASK) ==
1962 eStringBase) {
1963 nsStringBuffer* str = static_cast<nsStringBuffer*>(otherPtr);
1964 n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0;
1967 if (Type() == eCSSDeclaration && container->mValue.mCSSDeclaration) {
1968 // TODO: mCSSDeclaration might be owned by another object which
1969 // would make us count them twice, bug 677493.
1970 // Bug 1281964: For DeclarationBlock if we do measure we'll
1971 // need a way to call the Servo heap_size_of function.
1972 // n += container->mCSSDeclaration->SizeOfIncludingThis(aMallocSizeOf);
1973 } else if (Type() == eAtomArray && container->mValue.mAtomArray) {
1974 // Don't measure each nsAtom, they are measured separatly.
1975 n += container->mValue.mAtomArray->ShallowSizeOfIncludingThis(
1976 aMallocSizeOf);
1978 break;
1980 case eAtomBase: // Atoms are counted separately.
1981 case eIntegerBase: // The value is in mBits, nothing to do.
1982 break;
1985 return n;