diff options
Diffstat (limited to 'dom/base/nsAttrValue.cpp')
-rw-r--r-- | dom/base/nsAttrValue.cpp | 2170 |
1 files changed, 2170 insertions, 0 deletions
diff --git a/dom/base/nsAttrValue.cpp b/dom/base/nsAttrValue.cpp new file mode 100644 index 0000000000..ea7923fa0f --- /dev/null +++ b/dom/base/nsAttrValue.cpp @@ -0,0 +1,2170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * A struct that represents the value (type and actual data) of an + * attribute. + */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/HashFunctions.h" + +#include "nsAttrValue.h" +#include "nsAttrValueInlines.h" +#include "nsAtomHashKeys.h" +#include "nsUnicharUtils.h" +#include "mozilla/AttributeStyles.h" +#include "mozilla/BloomFilter.h" +#include "mozilla/CORSMode.h" +#include "mozilla/DeclarationBlock.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ServoBindingTypes.h" +#include "mozilla/ServoUtils.h" +#include "mozilla/ShadowParts.h" +#include "mozilla/SVGAttrValueWrapper.h" +#include "mozilla/URLExtraData.h" +#include "mozilla/dom/CSSRuleBinding.h" +#include "mozilla/dom/Document.h" +#include "nsContentUtils.h" +#include "nsReadableUtils.h" +#include "nsStyledElement.h" +#include "nsIURI.h" +#include "ReferrerInfo.h" +#include <algorithm> + +using namespace mozilla; + +constexpr uint32_t kMiscContainerCacheSize = 128; +static void* gMiscContainerCache[kMiscContainerCacheSize]; +static uint32_t gMiscContainerCount = 0; + +/** + * Global cache for eAtomArray MiscContainer objects, to speed up the parsing + * of class attributes with multiple class names. + * This cache doesn't keep anything alive - a MiscContainer removes itself from + * the cache once its last reference is dropped. + */ +struct AtomArrayCache { + // We don't keep any strong references, neither to the atom nor to the + // MiscContainer. The MiscContainer removes itself from the cache when + // the last reference to it is dropped, and the atom is kept alive by + // the MiscContainer. + using MapType = nsTHashMap<nsAtom*, MiscContainer*>; + + static MiscContainer* Lookup(nsAtom* aValue) { + if (auto* instance = GetInstance()) { + return instance->LookupImpl(aValue); + } + return nullptr; + } + + static void Insert(nsAtom* aValue, MiscContainer* aCont) { + if (auto* instance = GetInstance()) { + instance->InsertImpl(aValue, aCont); + } + } + + static void Remove(nsAtom* aValue) { + if (auto* instance = GetInstance()) { + instance->RemoveImpl(aValue); + } + } + + static AtomArrayCache* GetInstance() { + static StaticAutoPtr<AtomArrayCache> sInstance; + if (!sInstance && !PastShutdownPhase(ShutdownPhase::XPCOMShutdownFinal)) { + sInstance = new AtomArrayCache(); + ClearOnShutdown(&sInstance, ShutdownPhase::XPCOMShutdownFinal); + } + return sInstance; + } + + private: + MiscContainer* LookupImpl(nsAtom* aValue) { + auto lookupResult = mMap.Lookup(aValue); + return lookupResult ? *lookupResult : nullptr; + } + + void InsertImpl(nsAtom* aValue, MiscContainer* aCont) { + MOZ_ASSERT(aCont); + mMap.InsertOrUpdate(aValue, aCont); + } + + void RemoveImpl(nsAtom* aValue) { mMap.Remove(aValue); } + + MapType mMap; +}; + +/* static */ +MiscContainer* nsAttrValue::AllocMiscContainer() { + MOZ_ASSERT(NS_IsMainThread()); + + static_assert(sizeof(gMiscContainerCache) <= 1024); + static_assert(sizeof(MiscContainer) <= 32); + + // Allocate MiscContainer objects in batches to improve performance. + if (gMiscContainerCount == 0) { + for (; gMiscContainerCount < kMiscContainerCacheSize; + ++gMiscContainerCount) { + gMiscContainerCache[gMiscContainerCount] = + moz_xmalloc(sizeof(MiscContainer)); + } + } + + return new (gMiscContainerCache[--gMiscContainerCount]) MiscContainer(); +} + +/* static */ +void nsAttrValue::DeallocMiscContainer(MiscContainer* aCont) { + MOZ_ASSERT(NS_IsMainThread()); + if (!aCont) { + return; + } + + aCont->~MiscContainer(); + + if (gMiscContainerCount < kMiscContainerCacheSize) { + gMiscContainerCache[gMiscContainerCount++] = aCont; + return; + } + + free(aCont); +} + +bool MiscContainer::GetString(nsAString& aString) const { + bool isString; + void* ptr = GetStringOrAtomPtr(isString); + if (!ptr) { + return false; + } + if (isString) { + auto* buffer = static_cast<nsStringBuffer*>(ptr); + buffer->ToString(buffer->StorageSize() / sizeof(char16_t) - 1, aString); + } else { + static_cast<nsAtom*>(ptr)->ToString(aString); + } + return true; +} + +void MiscContainer::Cache() { + switch (mType) { + case nsAttrValue::eCSSDeclaration: { + MOZ_ASSERT(IsRefCounted()); + MOZ_ASSERT(mValue.mRefCount > 0); + MOZ_ASSERT(!mValue.mCached); + + AttributeStyles* attrStyles = + mValue.mCSSDeclaration->GetAttributeStyles(); + if (!attrStyles) { + return; + } + + nsString str; + bool gotString = GetString(str); + if (!gotString) { + return; + } + + attrStyles->CacheStyleAttr(str, this); + mValue.mCached = 1; + + // This has to be immutable once it goes into the cache. + mValue.mCSSDeclaration->SetImmutable(); + break; + } + case nsAttrValue::eAtomArray: { + MOZ_ASSERT(IsRefCounted()); + MOZ_ASSERT(mValue.mRefCount > 0); + MOZ_ASSERT(!mValue.mCached); + + nsAtom* atom = GetStoredAtom(); + if (!atom) { + return; + } + + AtomArrayCache::Insert(atom, this); + mValue.mCached = 1; + break; + } + default: + MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type"); + break; + } +} + +void MiscContainer::Evict() { + switch (mType) { + case nsAttrValue::eCSSDeclaration: { + MOZ_ASSERT(IsRefCounted()); + MOZ_ASSERT(mValue.mRefCount == 0); + + if (!mValue.mCached) { + return; + } + + AttributeStyles* attrStyles = + mValue.mCSSDeclaration->GetAttributeStyles(); + MOZ_ASSERT(attrStyles); + + nsString str; + DebugOnly<bool> gotString = GetString(str); + MOZ_ASSERT(gotString); + + attrStyles->EvictStyleAttr(str, this); + mValue.mCached = 0; + break; + } + case nsAttrValue::eAtomArray: { + MOZ_ASSERT(IsRefCounted()); + MOZ_ASSERT(mValue.mRefCount == 0); + + if (!mValue.mCached) { + return; + } + + nsAtom* atom = GetStoredAtom(); + MOZ_ASSERT(atom); + + AtomArrayCache::Remove(atom); + + mValue.mCached = 0; + break; + } + default: + + MOZ_ASSERT_UNREACHABLE("unexpected cached nsAttrValue type"); + break; + } +} + +nsTArray<const nsAttrValue::EnumTable*>* nsAttrValue::sEnumTableArray = nullptr; + +nsAttrValue::nsAttrValue() : mBits(0) {} + +nsAttrValue::nsAttrValue(const nsAttrValue& aOther) : mBits(0) { + SetTo(aOther); +} + +nsAttrValue::nsAttrValue(const nsAString& aValue) : mBits(0) { SetTo(aValue); } + +nsAttrValue::nsAttrValue(nsAtom* aValue) : mBits(0) { SetTo(aValue); } + +nsAttrValue::nsAttrValue(already_AddRefed<DeclarationBlock> aValue, + const nsAString* aSerialized) + : mBits(0) { + SetTo(std::move(aValue), aSerialized); +} + +nsAttrValue::~nsAttrValue() { ResetIfSet(); } + +/* static */ +void nsAttrValue::Init() { + MOZ_ASSERT(!sEnumTableArray, "nsAttrValue already initialized"); + sEnumTableArray = new nsTArray<const EnumTable*>; +} + +/* static */ +void nsAttrValue::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + delete sEnumTableArray; + sEnumTableArray = nullptr; + + for (uint32_t i = 0; i < gMiscContainerCount; ++i) { + free(gMiscContainerCache[i]); + } + gMiscContainerCount = 0; +} + +void nsAttrValue::Reset() { + switch (BaseType()) { + case eStringBase: { + nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr()); + if (str) { + str->Release(); + } + + break; + } + case eOtherBase: { + MiscContainer* cont = GetMiscContainer(); + if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) { + NS_RELEASE(cont); + break; + } + + DeallocMiscContainer(ClearMiscContainer()); + + break; + } + case eAtomBase: { + nsAtom* atom = GetAtomValue(); + NS_RELEASE(atom); + + break; + } + case eIntegerBase: { + break; + } + } + + mBits = 0; +} + +void nsAttrValue::SetTo(const nsAttrValue& aOther) { + if (this == &aOther) { + return; + } + + switch (aOther.BaseType()) { + case eStringBase: { + ResetIfSet(); + nsStringBuffer* str = static_cast<nsStringBuffer*>(aOther.GetPtr()); + if (str) { + str->AddRef(); + SetPtrValueAndType(str, eStringBase); + } + return; + } + case eOtherBase: { + break; + } + case eAtomBase: { + ResetIfSet(); + nsAtom* atom = aOther.GetAtomValue(); + NS_ADDREF(atom); + SetPtrValueAndType(atom, eAtomBase); + return; + } + case eIntegerBase: { + ResetIfSet(); + mBits = aOther.mBits; + return; + } + } + + MiscContainer* otherCont = aOther.GetMiscContainer(); + if (otherCont->IsRefCounted()) { + DeallocMiscContainer(ClearMiscContainer()); + NS_ADDREF(otherCont); + SetPtrValueAndType(otherCont, eOtherBase); + return; + } + + MiscContainer* cont = EnsureEmptyMiscContainer(); + switch (otherCont->mType) { + case eInteger: { + cont->mValue.mInteger = otherCont->mValue.mInteger; + break; + } + case eEnum: { + cont->mValue.mEnumValue = otherCont->mValue.mEnumValue; + break; + } + case ePercent: { + cont->mDoubleValue = otherCont->mDoubleValue; + break; + } + case eColor: { + cont->mValue.mColor = otherCont->mValue.mColor; + break; + } + case eAtomArray: + case eShadowParts: + case eCSSDeclaration: { + MOZ_CRASH("These should be refcounted!"); + } + case eURL: { + NS_ADDREF(cont->mValue.mURL = otherCont->mValue.mURL); + break; + } + case eDoubleValue: { + cont->mDoubleValue = otherCont->mDoubleValue; + break; + } + default: { + if (IsSVGType(otherCont->mType)) { + // All SVG types are just pointers to classes and will therefore have + // the same size so it doesn't really matter which one we assign + cont->mValue.mSVGLength = otherCont->mValue.mSVGLength; + } else { + MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer"); + } + break; + } + } + + bool isString; + if (void* otherPtr = otherCont->GetStringOrAtomPtr(isString)) { + if (isString) { + static_cast<nsStringBuffer*>(otherPtr)->AddRef(); + } else { + static_cast<nsAtom*>(otherPtr)->AddRef(); + } + cont->SetStringBitsMainThread(otherCont->mStringBits); + } + // Note, set mType after switch-case, otherwise EnsureEmptyAtomArray doesn't + // work correctly. + cont->mType = otherCont->mType; +} + +void nsAttrValue::SetTo(const nsAString& aValue) { + ResetIfSet(); + nsStringBuffer* buf = GetStringBuffer(aValue).take(); + if (buf) { + SetPtrValueAndType(buf, eStringBase); + } +} + +void nsAttrValue::SetTo(nsAtom* aValue) { + ResetIfSet(); + if (aValue) { + NS_ADDREF(aValue); + SetPtrValueAndType(aValue, eAtomBase); + } +} + +void nsAttrValue::SetTo(int16_t aInt) { + ResetIfSet(); + SetIntValueAndType(aInt, eInteger, nullptr); +} + +void nsAttrValue::SetTo(int32_t aInt, const nsAString* aSerialized) { + ResetIfSet(); + SetIntValueAndType(aInt, eInteger, aSerialized); +} + +void nsAttrValue::SetTo(double aValue, const nsAString* aSerialized) { + MiscContainer* cont = EnsureEmptyMiscContainer(); + cont->mDoubleValue = aValue; + cont->mType = eDoubleValue; + SetMiscAtomOrString(aSerialized); +} + +void nsAttrValue::SetTo(already_AddRefed<DeclarationBlock> aValue, + const nsAString* aSerialized) { + MiscContainer* cont = EnsureEmptyMiscContainer(); + MOZ_ASSERT(cont->mValue.mRefCount == 0); + cont->mValue.mCSSDeclaration = aValue.take(); + cont->mType = eCSSDeclaration; + NS_ADDREF(cont); + SetMiscAtomOrString(aSerialized); + MOZ_ASSERT(cont->mValue.mRefCount == 1); +} + +void nsAttrValue::SetTo(nsIURI* aValue, const nsAString* aSerialized) { + MiscContainer* cont = EnsureEmptyMiscContainer(); + NS_ADDREF(cont->mValue.mURL = aValue); + cont->mType = eURL; + SetMiscAtomOrString(aSerialized); +} + +void nsAttrValue::SetToSerialized(const nsAttrValue& aOther) { + if (aOther.Type() != nsAttrValue::eString && + aOther.Type() != nsAttrValue::eAtom) { + nsAutoString val; + aOther.ToString(val); + SetTo(val); + } else { + SetTo(aOther); + } +} + +void nsAttrValue::SetTo(const SVGAnimatedOrient& aValue, + const nsAString* aSerialized) { + SetSVGType(eSVGOrient, &aValue, aSerialized); +} + +void nsAttrValue::SetTo(const SVGAnimatedIntegerPair& aValue, + const nsAString* aSerialized) { + SetSVGType(eSVGIntegerPair, &aValue, aSerialized); +} + +void nsAttrValue::SetTo(const SVGAnimatedLength& aValue, + const nsAString* aSerialized) { + SetSVGType(eSVGLength, &aValue, aSerialized); +} + +void nsAttrValue::SetTo(const SVGLengthList& aValue, + const nsAString* aSerialized) { + // While an empty string will parse as a length list, there's no need to store + // it (and SetMiscAtomOrString will assert if we try) + if (aSerialized && aSerialized->IsEmpty()) { + aSerialized = nullptr; + } + SetSVGType(eSVGLengthList, &aValue, aSerialized); +} + +void nsAttrValue::SetTo(const SVGNumberList& aValue, + const nsAString* aSerialized) { + // While an empty string will parse as a number list, there's no need to store + // it (and SetMiscAtomOrString will assert if we try) + if (aSerialized && aSerialized->IsEmpty()) { + aSerialized = nullptr; + } + SetSVGType(eSVGNumberList, &aValue, aSerialized); +} + +void nsAttrValue::SetTo(const SVGAnimatedNumberPair& aValue, + const nsAString* aSerialized) { + SetSVGType(eSVGNumberPair, &aValue, aSerialized); +} + +void nsAttrValue::SetTo(const SVGPathData& aValue, + const nsAString* aSerialized) { + // While an empty string will parse as path data, there's no need to store it + // (and SetMiscAtomOrString will assert if we try) + if (aSerialized && aSerialized->IsEmpty()) { + aSerialized = nullptr; + } + SetSVGType(eSVGPathData, &aValue, aSerialized); +} + +void nsAttrValue::SetTo(const SVGPointList& aValue, + const nsAString* aSerialized) { + // While an empty string will parse as a point list, there's no need to store + // it (and SetMiscAtomOrString will assert if we try) + if (aSerialized && aSerialized->IsEmpty()) { + aSerialized = nullptr; + } + SetSVGType(eSVGPointList, &aValue, aSerialized); +} + +void nsAttrValue::SetTo(const SVGAnimatedPreserveAspectRatio& aValue, + const nsAString* aSerialized) { + SetSVGType(eSVGPreserveAspectRatio, &aValue, aSerialized); +} + +void nsAttrValue::SetTo(const SVGStringList& aValue, + const nsAString* aSerialized) { + // While an empty string will parse as a string list, there's no need to store + // it (and SetMiscAtomOrString will assert if we try) + if (aSerialized && aSerialized->IsEmpty()) { + aSerialized = nullptr; + } + SetSVGType(eSVGStringList, &aValue, aSerialized); +} + +void nsAttrValue::SetTo(const SVGTransformList& aValue, + const nsAString* aSerialized) { + // While an empty string will parse as a transform list, there's no need to + // store it (and SetMiscAtomOrString will assert if we try) + if (aSerialized && aSerialized->IsEmpty()) { + aSerialized = nullptr; + } + SetSVGType(eSVGTransformList, &aValue, aSerialized); +} + +void nsAttrValue::SetTo(const SVGAnimatedViewBox& aValue, + const nsAString* aSerialized) { + SetSVGType(eSVGViewBox, &aValue, aSerialized); +} + +void nsAttrValue::SwapValueWith(nsAttrValue& aOther) { + uintptr_t tmp = aOther.mBits; + aOther.mBits = mBits; + mBits = tmp; +} + +void nsAttrValue::RemoveDuplicatesFromAtomArray() { + if (Type() != eAtomArray) { + return; + } + + const AttrAtomArray* currentAtomArray = GetMiscContainer()->mValue.mAtomArray; + UniquePtr<AttrAtomArray> deduplicatedAtomArray = + currentAtomArray->CreateDeduplicatedCopyIfDifferent(); + + if (!deduplicatedAtomArray) { + // No duplicates found. Leave this value unchanged. + return; + } + + // We found duplicates. Wrap the new atom array into a fresh MiscContainer, + // and copy over the existing container's string or atom. + + MiscContainer* oldCont = GetMiscContainer(); + MOZ_ASSERT(oldCont->IsRefCounted()); + + uintptr_t stringBits = 0; + bool isString = false; + if (void* otherPtr = oldCont->GetStringOrAtomPtr(isString)) { + stringBits = oldCont->mStringBits; + if (isString) { + static_cast<nsStringBuffer*>(otherPtr)->AddRef(); + } else { + static_cast<nsAtom*>(otherPtr)->AddRef(); + } + } + + MiscContainer* cont = EnsureEmptyMiscContainer(); + MOZ_ASSERT(cont->mValue.mRefCount == 0); + cont->mValue.mAtomArray = deduplicatedAtomArray.release(); + cont->mType = eAtomArray; + NS_ADDREF(cont); + MOZ_ASSERT(cont->mValue.mRefCount == 1); + cont->SetStringBitsMainThread(stringBits); + + // Don't cache the new container. It would stomp over the undeduplicated + // value in the cache. But we could have a separate cache for deduplicated + // atom arrays, if repeated deduplication shows up in profiles. +} + +void nsAttrValue::ToString(nsAString& aResult) const { + MiscContainer* cont = nullptr; + if (BaseType() == eOtherBase) { + cont = GetMiscContainer(); + + if (cont->GetString(aResult)) { + return; + } + } + + switch (Type()) { + case eString: { + nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr()); + if (str) { + str->ToString(str->StorageSize() / sizeof(char16_t) - 1, aResult); + } else { + aResult.Truncate(); + } + break; + } + case eAtom: { + nsAtom* atom = static_cast<nsAtom*>(GetPtr()); + atom->ToString(aResult); + + break; + } + case eInteger: { + nsAutoString intStr; + intStr.AppendInt(GetIntegerValue()); + aResult = intStr; + + break; + } +#ifdef DEBUG + case eColor: { + MOZ_ASSERT_UNREACHABLE("color attribute without string data"); + aResult.Truncate(); + break; + } +#endif + case eEnum: { + GetEnumString(aResult, false); + break; + } + case ePercent: { + nsAutoString str; + if (cont) { + str.AppendFloat(cont->mDoubleValue); + } else { + str.AppendInt(GetIntInternal()); + } + aResult = str + u"%"_ns; + + break; + } + case eCSSDeclaration: { + aResult.Truncate(); + MiscContainer* container = GetMiscContainer(); + if (DeclarationBlock* decl = container->mValue.mCSSDeclaration) { + nsAutoCString result; + decl->ToString(result); + CopyUTF8toUTF16(result, aResult); + } + + // This can be reached during parallel selector matching with attribute + // selectors on the style attribute. SetMiscAtomOrString handles this + // case, and as of this writing this is the only consumer that needs it. + const_cast<nsAttrValue*>(this)->SetMiscAtomOrString(&aResult); + + break; + } + case eDoubleValue: { + aResult.Truncate(); + aResult.AppendFloat(GetDoubleValue()); + break; + } + case eSVGIntegerPair: { + SVGAttrValueWrapper::ToString( + GetMiscContainer()->mValue.mSVGAnimatedIntegerPair, aResult); + break; + } + case eSVGOrient: { + SVGAttrValueWrapper::ToString( + GetMiscContainer()->mValue.mSVGAnimatedOrient, aResult); + break; + } + case eSVGLength: { + SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLength, + aResult); + break; + } + case eSVGLengthList: { + SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGLengthList, + aResult); + break; + } + case eSVGNumberList: { + SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGNumberList, + aResult); + break; + } + case eSVGNumberPair: { + SVGAttrValueWrapper::ToString( + GetMiscContainer()->mValue.mSVGAnimatedNumberPair, aResult); + break; + } + case eSVGPathData: { + SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPathData, + aResult); + break; + } + case eSVGPointList: { + SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGPointList, + aResult); + break; + } + case eSVGPreserveAspectRatio: { + SVGAttrValueWrapper::ToString( + GetMiscContainer()->mValue.mSVGAnimatedPreserveAspectRatio, aResult); + break; + } + case eSVGStringList: { + SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGStringList, + aResult); + break; + } + case eSVGTransformList: { + SVGAttrValueWrapper::ToString( + GetMiscContainer()->mValue.mSVGTransformList, aResult); + break; + } + case eSVGViewBox: { + SVGAttrValueWrapper::ToString( + GetMiscContainer()->mValue.mSVGAnimatedViewBox, aResult); + break; + } + default: { + aResult.Truncate(); + break; + } + } +} + +already_AddRefed<nsAtom> nsAttrValue::GetAsAtom() const { + switch (Type()) { + case eString: + return NS_AtomizeMainThread(GetStringValue()); + + case eAtom: { + RefPtr<nsAtom> atom = GetAtomValue(); + return atom.forget(); + } + + default: { + nsAutoString val; + ToString(val); + return NS_AtomizeMainThread(val); + } + } +} + +const nsCheapString nsAttrValue::GetStringValue() const { + MOZ_ASSERT(Type() == eString, "wrong type"); + + return nsCheapString(static_cast<nsStringBuffer*>(GetPtr())); +} + +bool nsAttrValue::GetColorValue(nscolor& aColor) const { + if (Type() != eColor) { + // Unparseable value, treat as unset. + NS_ASSERTION(Type() == eString, "unexpected type for color-valued attr"); + return false; + } + + aColor = GetMiscContainer()->mValue.mColor; + return true; +} + +void nsAttrValue::GetEnumString(nsAString& aResult, bool aRealTag) const { + MOZ_ASSERT(Type() == eEnum, "wrong type"); + + uint32_t allEnumBits = (BaseType() == eIntegerBase) + ? static_cast<uint32_t>(GetIntInternal()) + : GetMiscContainer()->mValue.mEnumValue; + int16_t val = allEnumBits >> NS_ATTRVALUE_ENUMTABLEINDEX_BITS; + const EnumTable* table = sEnumTableArray->ElementAt( + allEnumBits & NS_ATTRVALUE_ENUMTABLEINDEX_MASK); + + while (table->tag) { + if (table->value == val) { + aResult.AssignASCII(table->tag); + if (!aRealTag && + allEnumBits & NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER) { + nsContentUtils::ASCIIToUpper(aResult); + } + return; + } + table++; + } + + MOZ_ASSERT_UNREACHABLE("couldn't find value in EnumTable"); +} + +UniquePtr<AttrAtomArray> AttrAtomArray::CreateDeduplicatedCopyIfDifferentImpl() + const { + MOZ_ASSERT(mMayContainDuplicates); + + bool usingHashTable = false; + BitBloomFilter<8, nsAtom> filter; + nsTHashSet<nsAtom*> hash; + + auto CheckDuplicate = [&](size_t i) { + nsAtom* atom = mArray[i]; + if (!usingHashTable) { + if (!filter.mightContain(atom)) { + filter.add(atom); + return false; + } + for (size_t j = 0; j < i; ++j) { + hash.Insert(mArray[j]); + } + usingHashTable = true; + } + return !hash.EnsureInserted(atom); + }; + + size_t len = mArray.Length(); + UniquePtr<AttrAtomArray> deduplicatedArray; + for (size_t i = 0; i < len; ++i) { + if (!CheckDuplicate(i)) { + if (deduplicatedArray) { + deduplicatedArray->mArray.AppendElement(mArray[i]); + } + continue; + } + // We've found a duplicate! + if (!deduplicatedArray) { + // Allocate the deduplicated copy and copy the preceding elements into it. + deduplicatedArray = MakeUnique<AttrAtomArray>(); + deduplicatedArray->mMayContainDuplicates = false; + deduplicatedArray->mArray.SetCapacity(len - 1); + for (size_t indexToCopy = 0; indexToCopy < i; indexToCopy++) { + deduplicatedArray->mArray.AppendElement(mArray[indexToCopy]); + } + } + } + + if (!deduplicatedArray) { + // This AttrAtomArray doesn't contain any duplicates, cache this information + // for future invocations. + mMayContainDuplicates = false; + } + return deduplicatedArray; +} + +uint32_t nsAttrValue::GetAtomCount() const { + ValueType type = Type(); + + if (type == eAtom) { + return 1; + } + + if (type == eAtomArray) { + return GetAtomArrayValue()->mArray.Length(); + } + + return 0; +} + +nsAtom* nsAttrValue::AtomAt(int32_t aIndex) const { + MOZ_ASSERT(aIndex >= 0, "Index must not be negative"); + MOZ_ASSERT(GetAtomCount() > uint32_t(aIndex), "aIndex out of range"); + + if (BaseType() == eAtomBase) { + return GetAtomValue(); + } + + NS_ASSERTION(Type() == eAtomArray, "GetAtomCount must be confused"); + return GetAtomArrayValue()->mArray.ElementAt(aIndex); +} + +uint32_t nsAttrValue::HashValue() const { + switch (BaseType()) { + case eStringBase: { + nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr()); + if (str) { + uint32_t len = str->StorageSize() / sizeof(char16_t) - 1; + return HashString(static_cast<char16_t*>(str->Data()), len); + } + + return 0; + } + case eOtherBase: { + break; + } + case eAtomBase: + case eIntegerBase: { + // mBits and uint32_t might have different size. This should silence + // any warnings or compile-errors. This is what the implementation of + // NS_PTR_TO_INT32 does to take care of the same problem. + return mBits - 0; + } + } + + MiscContainer* cont = GetMiscContainer(); + if (static_cast<ValueBaseType>(cont->mStringBits & + NS_ATTRVALUE_BASETYPE_MASK) == eAtomBase) { + return cont->mStringBits - 0; + } + + switch (cont->mType) { + case eInteger: { + return cont->mValue.mInteger; + } + case eEnum: { + return cont->mValue.mEnumValue; + } + case ePercent: { + return cont->mDoubleValue; + } + case eColor: { + return cont->mValue.mColor; + } + case eCSSDeclaration: { + return NS_PTR_TO_INT32(cont->mValue.mCSSDeclaration); + } + case eURL: { + nsString str; + ToString(str); + return HashString(str); + } + case eAtomArray: { + uint32_t hash = 0; + for (const auto& atom : cont->mValue.mAtomArray->mArray) { + hash = AddToHash(hash, atom.get()); + } + return hash; + } + case eDoubleValue: { + // XXX this is crappy, but oh well + return cont->mDoubleValue; + } + default: { + if (IsSVGType(cont->mType)) { + // All SVG types are just pointers to classes so we can treat them alike + return NS_PTR_TO_INT32(cont->mValue.mSVGLength); + } + MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer"); + return 0; + } + } +} + +bool nsAttrValue::Equals(const nsAttrValue& aOther) const { + if (BaseType() != aOther.BaseType()) { + return false; + } + + switch (BaseType()) { + case eStringBase: { + return GetStringValue().Equals(aOther.GetStringValue()); + } + case eOtherBase: { + break; + } + case eAtomBase: + case eIntegerBase: { + return mBits == aOther.mBits; + } + } + + MiscContainer* thisCont = GetMiscContainer(); + MiscContainer* otherCont = aOther.GetMiscContainer(); + if (thisCont == otherCont) { + return true; + } + + if (thisCont->mType != otherCont->mType) { + return false; + } + + bool needsStringComparison = false; + + switch (thisCont->mType) { + case eInteger: { + if (thisCont->mValue.mInteger == otherCont->mValue.mInteger) { + needsStringComparison = true; + } + break; + } + case eEnum: { + if (thisCont->mValue.mEnumValue == otherCont->mValue.mEnumValue) { + needsStringComparison = true; + } + break; + } + case ePercent: { + if (thisCont->mDoubleValue == otherCont->mDoubleValue) { + needsStringComparison = true; + } + break; + } + case eColor: { + if (thisCont->mValue.mColor == otherCont->mValue.mColor) { + needsStringComparison = true; + } + break; + } + case eCSSDeclaration: { + return thisCont->mValue.mCSSDeclaration == + otherCont->mValue.mCSSDeclaration; + } + case eURL: { + return thisCont->mValue.mURL == otherCont->mValue.mURL; + } + case eAtomArray: { + // For classlists we could be insensitive to order, however + // classlists are never mapped attributes so they are never compared. + + if (!(*thisCont->mValue.mAtomArray == *otherCont->mValue.mAtomArray)) { + return false; + } + + needsStringComparison = true; + break; + } + case eDoubleValue: { + return thisCont->mDoubleValue == otherCont->mDoubleValue; + } + default: { + if (IsSVGType(thisCont->mType)) { + // Currently this method is never called for nsAttrValue objects that + // point to SVG data types. + // If that changes then we probably want to add methods to the + // corresponding SVG types to compare their base values. + // As a shortcut, however, we can begin by comparing the pointers. + MOZ_ASSERT(false, "Comparing nsAttrValues that point to SVG data"); + return false; + } + MOZ_ASSERT_UNREACHABLE("unknown type stored in MiscContainer"); + return false; + } + } + if (needsStringComparison) { + if (thisCont->mStringBits == otherCont->mStringBits) { + return true; + } + if ((static_cast<ValueBaseType>(thisCont->mStringBits & + NS_ATTRVALUE_BASETYPE_MASK) == + eStringBase) && + (static_cast<ValueBaseType>(otherCont->mStringBits & + NS_ATTRVALUE_BASETYPE_MASK) == + eStringBase)) { + return nsCheapString(reinterpret_cast<nsStringBuffer*>( + static_cast<uintptr_t>(thisCont->mStringBits))) + .Equals(nsCheapString(reinterpret_cast<nsStringBuffer*>( + static_cast<uintptr_t>(otherCont->mStringBits)))); + } + } + return false; +} + +bool nsAttrValue::Equals(const nsAString& aValue, + nsCaseTreatment aCaseSensitive) const { + switch (BaseType()) { + case eStringBase: { + if (auto* str = static_cast<nsStringBuffer*>(GetPtr())) { + nsDependentString dep(static_cast<char16_t*>(str->Data()), + str->StorageSize() / sizeof(char16_t) - 1); + return aCaseSensitive == eCaseMatters + ? aValue.Equals(dep) + : nsContentUtils::EqualsIgnoreASCIICase(aValue, dep); + } + return aValue.IsEmpty(); + } + case eAtomBase: { + auto* atom = static_cast<nsAtom*>(GetPtr()); + if (aCaseSensitive == eCaseMatters) { + return atom->Equals(aValue); + } + return nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(atom), + aValue); + } + default: + break; + } + + nsAutoString val; + ToString(val); + return aCaseSensitive == eCaseMatters + ? val.Equals(aValue) + : nsContentUtils::EqualsIgnoreASCIICase(val, aValue); +} + +bool nsAttrValue::Equals(const nsAtom* aValue, + nsCaseTreatment aCaseSensitive) const { + switch (BaseType()) { + case eAtomBase: { + auto* atom = static_cast<nsAtom*>(GetPtr()); + if (atom == aValue) { + return true; + } + if (aCaseSensitive == eCaseMatters) { + return false; + } + if (atom->IsAsciiLowercase() && aValue->IsAsciiLowercase()) { + return false; + } + return nsContentUtils::EqualsIgnoreASCIICase( + nsDependentAtomString(atom), nsDependentAtomString(aValue)); + } + case eStringBase: { + if (auto* str = static_cast<nsStringBuffer*>(GetPtr())) { + size_t strLen = str->StorageSize() / sizeof(char16_t) - 1; + if (aValue->GetLength() != strLen) { + return false; + } + const char16_t* strData = static_cast<char16_t*>(str->Data()); + const char16_t* valData = aValue->GetUTF16String(); + if (aCaseSensitive == eCaseMatters) { + // Avoid string construction / destruction for the easy case. + return ArrayEqual(strData, valData, strLen); + } + nsDependentSubstring depStr(strData, strLen); + nsDependentSubstring depVal(valData, strLen); + return nsContentUtils::EqualsIgnoreASCIICase(depStr, depVal); + } + return aValue->IsEmpty(); + } + default: + break; + } + + nsAutoString val; + ToString(val); + nsDependentAtomString dep(aValue); + return aCaseSensitive == eCaseMatters + ? val.Equals(dep) + : nsContentUtils::EqualsIgnoreASCIICase(val, dep); +} + +struct HasPrefixFn { + static bool Check(const char16_t* aAttrValue, size_t aAttrLen, + const nsAString& aSearchValue, + nsCaseTreatment aCaseSensitive) { + if (aCaseSensitive == eCaseMatters) { + if (aSearchValue.Length() > aAttrLen) { + return false; + } + return !memcmp(aAttrValue, aSearchValue.BeginReading(), + aSearchValue.Length() * sizeof(char16_t)); + } + return StringBeginsWith(nsDependentString(aAttrValue, aAttrLen), + aSearchValue, + nsASCIICaseInsensitiveStringComparator); + } +}; + +struct HasSuffixFn { + static bool Check(const char16_t* aAttrValue, size_t aAttrLen, + const nsAString& aSearchValue, + nsCaseTreatment aCaseSensitive) { + if (aCaseSensitive == eCaseMatters) { + if (aSearchValue.Length() > aAttrLen) { + return false; + } + return !memcmp(aAttrValue + aAttrLen - aSearchValue.Length(), + aSearchValue.BeginReading(), + aSearchValue.Length() * sizeof(char16_t)); + } + return StringEndsWith(nsDependentString(aAttrValue, aAttrLen), aSearchValue, + nsASCIICaseInsensitiveStringComparator); + } +}; + +struct HasSubstringFn { + static bool Check(const char16_t* aAttrValue, size_t aAttrLen, + const nsAString& aSearchValue, + nsCaseTreatment aCaseSensitive) { + if (aCaseSensitive == eCaseMatters) { + if (aSearchValue.IsEmpty()) { + return true; + } + const char16_t* end = aAttrValue + aAttrLen; + return std::search(aAttrValue, end, aSearchValue.BeginReading(), + aSearchValue.EndReading()) != end; + } + return FindInReadable(aSearchValue, nsDependentString(aAttrValue, aAttrLen), + nsASCIICaseInsensitiveStringComparator); + } +}; + +template <typename F> +bool nsAttrValue::SubstringCheck(const nsAString& aValue, + nsCaseTreatment aCaseSensitive) const { + switch (BaseType()) { + case eStringBase: { + auto str = static_cast<nsStringBuffer*>(GetPtr()); + if (str) { + return F::Check(static_cast<char16_t*>(str->Data()), + str->StorageSize() / sizeof(char16_t) - 1, aValue, + aCaseSensitive); + } + return aValue.IsEmpty(); + } + case eAtomBase: { + auto atom = static_cast<nsAtom*>(GetPtr()); + return F::Check(atom->GetUTF16String(), atom->GetLength(), aValue, + aCaseSensitive); + } + default: + break; + } + + nsAutoString val; + ToString(val); + return F::Check(val.BeginReading(), val.Length(), aValue, aCaseSensitive); +} + +bool nsAttrValue::HasPrefix(const nsAString& aValue, + nsCaseTreatment aCaseSensitive) const { + return SubstringCheck<HasPrefixFn>(aValue, aCaseSensitive); +} + +bool nsAttrValue::HasSuffix(const nsAString& aValue, + nsCaseTreatment aCaseSensitive) const { + return SubstringCheck<HasSuffixFn>(aValue, aCaseSensitive); +} + +bool nsAttrValue::HasSubstring(const nsAString& aValue, + nsCaseTreatment aCaseSensitive) const { + return SubstringCheck<HasSubstringFn>(aValue, aCaseSensitive); +} + +bool nsAttrValue::EqualsAsStrings(const nsAttrValue& aOther) const { + if (Type() == aOther.Type()) { + return Equals(aOther); + } + + // We need to serialize at least one nsAttrValue before passing to + // Equals(const nsAString&), but we can avoid unnecessarily serializing both + // by checking if one is already of a string type. + bool thisIsString = (BaseType() == eStringBase || BaseType() == eAtomBase); + const nsAttrValue& lhs = thisIsString ? *this : aOther; + const nsAttrValue& rhs = thisIsString ? aOther : *this; + + switch (rhs.BaseType()) { + case eAtomBase: + return lhs.Equals(rhs.GetAtomValue(), eCaseMatters); + + case eStringBase: + return lhs.Equals(rhs.GetStringValue(), eCaseMatters); + + default: { + nsAutoString val; + rhs.ToString(val); + return lhs.Equals(val, eCaseMatters); + } + } +} + +bool nsAttrValue::Contains(nsAtom* aValue, + nsCaseTreatment aCaseSensitive) const { + switch (BaseType()) { + case eAtomBase: { + nsAtom* atom = GetAtomValue(); + if (aCaseSensitive == eCaseMatters) { + return aValue == atom; + } + + // For performance reasons, don't do a full on unicode case insensitive + // string comparison. This is only used for quirks mode anyway. + return nsContentUtils::EqualsIgnoreASCIICase(aValue, atom); + } + default: { + if (Type() == eAtomArray) { + const AttrAtomArray* array = GetAtomArrayValue(); + if (aCaseSensitive == eCaseMatters) { + return array->mArray.Contains(aValue); + } + + for (const RefPtr<nsAtom>& cur : array->mArray) { + // For performance reasons, don't do a full on unicode case + // insensitive string comparison. This is only used for quirks mode + // anyway. + if (nsContentUtils::EqualsIgnoreASCIICase(aValue, cur)) { + return true; + } + } + } + } + } + + return false; +} + +struct AtomArrayStringComparator { + bool Equals(nsAtom* atom, const nsAString& string) const { + return atom->Equals(string); + } +}; + +bool nsAttrValue::Contains(const nsAString& aValue) const { + switch (BaseType()) { + case eAtomBase: { + nsAtom* atom = GetAtomValue(); + return atom->Equals(aValue); + } + default: { + if (Type() == eAtomArray) { + const AttrAtomArray* array = GetAtomArrayValue(); + return array->mArray.Contains(aValue, AtomArrayStringComparator()); + } + } + } + + return false; +} + +void nsAttrValue::ParseAtom(const nsAString& aValue) { + ResetIfSet(); + + RefPtr<nsAtom> atom = NS_Atomize(aValue); + if (atom) { + SetPtrValueAndType(atom.forget().take(), eAtomBase); + } +} + +void nsAttrValue::ParseAtomArray(nsAtom* aValue) { + if (MiscContainer* cont = AtomArrayCache::Lookup(aValue)) { + // Set our MiscContainer to the cached one. + NS_ADDREF(cont); + SetPtrValueAndType(cont, eOtherBase); + return; + } + + const char16_t* iter = aValue->GetUTF16String(); + const char16_t* end = iter + aValue->GetLength(); + bool hasSpace = false; + + // skip initial whitespace + while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { + hasSpace = true; + ++iter; + } + + if (iter == end) { + // The value is empty or only contains whitespace. + // Set this attribute to the string value. + // We don't call the SetTo(nsAtom*) overload because doing so would + // leave us with a classList of length 1. + SetTo(nsDependentAtomString(aValue)); + return; + } + + const char16_t* start = iter; + + // get first - and often only - atom + do { + ++iter; + } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter)); + + RefPtr<nsAtom> classAtom = iter == end && !hasSpace + ? RefPtr<nsAtom>(aValue).forget() + : NS_AtomizeMainThread(Substring(start, iter)); + if (!classAtom) { + ResetIfSet(); + return; + } + + // skip whitespace + while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { + hasSpace = true; + ++iter; + } + + if (iter == end && !hasSpace) { + // we only found one classname and there was no whitespace so + // don't bother storing a list + ResetIfSet(); + nsAtom* atom = nullptr; + classAtom.swap(atom); + SetPtrValueAndType(atom, eAtomBase); + return; + } + + // We have at least one class atom. Create a new AttrAtomArray. + AttrAtomArray* array = new AttrAtomArray; + + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + array->mArray.AppendElement(std::move(classAtom)); + + // parse the rest of the classnames + while (iter != end) { + start = iter; + + do { + ++iter; + } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter)); + + classAtom = NS_AtomizeMainThread(Substring(start, iter)); + + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + array->mArray.AppendElement(std::move(classAtom)); + array->mMayContainDuplicates = true; + + // skip whitespace + while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { + ++iter; + } + } + + // Wrap the AtomArray into a fresh MiscContainer. + MiscContainer* cont = EnsureEmptyMiscContainer(); + MOZ_ASSERT(cont->mValue.mRefCount == 0); + cont->mValue.mAtomArray = array; + cont->mType = eAtomArray; + NS_ADDREF(cont); + MOZ_ASSERT(cont->mValue.mRefCount == 1); + + // Assign the atom to the container's string bits (like SetMiscAtomOrString + // would do). + MOZ_ASSERT(!IsInServoTraversal()); + aValue->AddRef(); + uintptr_t bits = reinterpret_cast<uintptr_t>(aValue) | eAtomBase; + cont->SetStringBitsMainThread(bits); + + // Put the container in the cache. + cont->Cache(); +} + +void nsAttrValue::ParseAtomArray(const nsAString& aValue) { + if (aValue.IsVoid()) { + ResetIfSet(); + } else { + RefPtr<nsAtom> atom = NS_AtomizeMainThread(aValue); + ParseAtomArray(atom); + } +} + +void nsAttrValue::ParseStringOrAtom(const nsAString& aValue) { + uint32_t len = aValue.Length(); + // Don't bother with atoms if it's an empty string since + // we can store those efficiently anyway. + if (len && len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) { + ParseAtom(aValue); + } else { + SetTo(aValue); + } +} + +void nsAttrValue::ParsePartMapping(const nsAString& aValue) { + ResetIfSet(); + MiscContainer* cont = EnsureEmptyMiscContainer(); + + cont->mType = eShadowParts; + cont->mValue.mShadowParts = new ShadowParts(ShadowParts::Parse(aValue)); + NS_ADDREF(cont); + SetMiscAtomOrString(&aValue); + MOZ_ASSERT(cont->mValue.mRefCount == 1); +} + +void nsAttrValue::SetIntValueAndType(int32_t aValue, ValueType aType, + const nsAString* aStringValue) { + if (aStringValue || aValue > NS_ATTRVALUE_INTEGERTYPE_MAXVALUE || + aValue < NS_ATTRVALUE_INTEGERTYPE_MINVALUE) { + MiscContainer* cont = EnsureEmptyMiscContainer(); + switch (aType) { + case eInteger: { + cont->mValue.mInteger = aValue; + break; + } + case ePercent: { + cont->mDoubleValue = aValue; + break; + } + case eEnum: { + cont->mValue.mEnumValue = aValue; + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unknown integer type"); + break; + } + } + cont->mType = aType; + SetMiscAtomOrString(aStringValue); + } else { + NS_ASSERTION(!mBits, "Reset before calling SetIntValueAndType!"); + mBits = (aValue * NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER) | aType; + } +} + +void nsAttrValue::SetDoubleValueAndType(double aValue, ValueType aType, + const nsAString* aStringValue) { + MOZ_ASSERT(aType == eDoubleValue || aType == ePercent, "Unexpected type"); + MiscContainer* cont = EnsureEmptyMiscContainer(); + cont->mDoubleValue = aValue; + cont->mType = aType; + SetMiscAtomOrString(aStringValue); +} + +nsAtom* nsAttrValue::GetStoredAtom() const { + if (BaseType() == eAtomBase) { + return static_cast<nsAtom*>(GetPtr()); + } + if (BaseType() == eOtherBase) { + return GetMiscContainer()->GetStoredAtom(); + } + return nullptr; +} + +nsStringBuffer* nsAttrValue::GetStoredStringBuffer() const { + if (BaseType() == eStringBase) { + return static_cast<nsStringBuffer*>(GetPtr()); + } + if (BaseType() == eOtherBase) { + return GetMiscContainer()->GetStoredStringBuffer(); + } + return nullptr; +} + +int16_t nsAttrValue::GetEnumTableIndex(const EnumTable* aTable) { + int16_t index = sEnumTableArray->IndexOf(aTable); + if (index < 0) { + index = sEnumTableArray->Length(); + NS_ASSERTION(index <= NS_ATTRVALUE_ENUMTABLEINDEX_MAXVALUE, + "too many enum tables"); + sEnumTableArray->AppendElement(aTable); + } + + return index; +} + +int32_t nsAttrValue::EnumTableEntryToValue(const EnumTable* aEnumTable, + const EnumTable* aTableEntry) { + int16_t index = GetEnumTableIndex(aEnumTable); + int32_t value = + (aTableEntry->value << NS_ATTRVALUE_ENUMTABLEINDEX_BITS) + index; + return value; +} + +bool nsAttrValue::ParseEnumValue(const nsAString& aValue, + const EnumTable* aTable, bool aCaseSensitive, + const EnumTable* aDefaultValue) { + ResetIfSet(); + const EnumTable* tableEntry = aTable; + + while (tableEntry->tag) { + if (aCaseSensitive ? aValue.EqualsASCII(tableEntry->tag) + : aValue.LowerCaseEqualsASCII(tableEntry->tag)) { + int32_t value = EnumTableEntryToValue(aTable, tableEntry); + + bool equals = aCaseSensitive || aValue.EqualsASCII(tableEntry->tag); + if (!equals) { + nsAutoString tag; + tag.AssignASCII(tableEntry->tag); + nsContentUtils::ASCIIToUpper(tag); + if ((equals = tag.Equals(aValue))) { + value |= NS_ATTRVALUE_ENUMTABLE_VALUE_NEEDS_TO_UPPER; + } + } + SetIntValueAndType(value, eEnum, equals ? nullptr : &aValue); + NS_ASSERTION(GetEnumValue() == tableEntry->value, + "failed to store enum properly"); + + return true; + } + tableEntry++; + } + + if (aDefaultValue) { + MOZ_ASSERT(aTable <= aDefaultValue && aDefaultValue < tableEntry, + "aDefaultValue not inside aTable?"); + SetIntValueAndType(EnumTableEntryToValue(aTable, aDefaultValue), eEnum, + &aValue); + return true; + } + + return false; +} + +bool nsAttrValue::DoParseHTMLDimension(const nsAString& aInput, + bool aEnsureNonzero) { + ResetIfSet(); + + // We don't use nsContentUtils::ParseHTMLInteger here because we + // need a bunch of behavioral differences from it. We _could_ try to + // use it, but it would not be a great fit. + + // https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values + + // Steps 1 and 2. + const char16_t* position = aInput.BeginReading(); + const char16_t* end = aInput.EndReading(); + + // We will need to keep track of whether this was a canonical representation + // or not. It's non-canonical if it has leading whitespace, leading '+', + // leading '0' characters, or trailing garbage. + bool canonical = true; + + // Step 3. + while (position != end && nsContentUtils::IsHTMLWhitespace(*position)) { + canonical = false; // Leading whitespace + ++position; + } + + // Step 4. + if (position == end || *position < char16_t('0') || + *position > char16_t('9')) { + return false; + } + + // Step 5. + CheckedInt32 value = 0; + + // Collect up leading '0' first to avoid extra branching in the main + // loop to set 'canonical' properly. + while (position != end && *position == char16_t('0')) { + canonical = false; // Leading '0' + ++position; + } + + // Now collect up other digits. + while (position != end && *position >= char16_t('0') && + *position <= char16_t('9')) { + value = value * 10 + (*position - char16_t('0')); + if (!value.isValid()) { + // The spec assumes we can deal with arbitrary-size integers here, but we + // really can't. If someone sets something too big, just bail out and + // ignore it. + return false; + } + ++position; + } + + // Step 6 is implemented implicitly via the various "position != end" guards + // from this point on. + + Maybe<double> doubleValue; + // Step 7. The return in step 7.2 is handled by just falling through to the + // code below this block when we reach end of input or a non-digit, because + // the while loop will terminate at that point. + if (position != end && *position == char16_t('.')) { + canonical = false; // Let's not rely on double serialization reproducing + // the string we started with. + // Step 7.1. + ++position; + // If we have a '.' _not_ followed by digits, this is not as efficient as it + // could be, because we will store as a double while we could have stored as + // an int. But that seems like a pretty rare case. + doubleValue.emplace(value.value()); + // Step 7.3. + double divisor = 1.0f; + // Step 7.4. + while (position != end && *position >= char16_t('0') && + *position <= char16_t('9')) { + // Step 7.4.1. + divisor = divisor * 10.0f; + // Step 7.4.2. + doubleValue.ref() += (*position - char16_t('0')) / divisor; + // Step 7.4.3. + ++position; + // Step 7.4.4 and 7.4.5 are captured in the while loop condition and the + // "position != end" checks below. + } + } + + if (aEnsureNonzero && value.value() == 0 && + (!doubleValue || *doubleValue == 0.0f)) { + // Not valid. Just drop it. + return false; + } + + // Step 8 and the spec's early return from step 7.2. + ValueType type; + if (position != end && *position == char16_t('%')) { + type = ePercent; + ++position; + } else if (doubleValue) { + type = eDoubleValue; + } else { + type = eInteger; + } + + if (position != end) { + canonical = false; + } + + if (doubleValue) { + MOZ_ASSERT(!canonical, "We set it false above!"); + SetDoubleValueAndType(*doubleValue, type, &aInput); + } else { + SetIntValueAndType(value.value(), type, canonical ? nullptr : &aInput); + } + +#ifdef DEBUG + nsAutoString str; + ToString(str); + MOZ_ASSERT(str == aInput, "We messed up our 'canonical' boolean!"); +#endif + + return true; +} + +bool nsAttrValue::ParseIntWithBounds(const nsAString& aString, int32_t aMin, + int32_t aMax) { + MOZ_ASSERT(aMin < aMax, "bad boundaries"); + + ResetIfSet(); + + nsContentUtils::ParseHTMLIntegerResultFlags result; + int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result); + if (result & nsContentUtils::eParseHTMLInteger_Error) { + return false; + } + + int32_t val = std::max(originalVal, aMin); + val = std::min(val, aMax); + bool nonStrict = + (val != originalVal) || + (result & nsContentUtils::eParseHTMLInteger_NonStandard) || + (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput); + + SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr); + + return true; +} + +void nsAttrValue::ParseIntWithFallback(const nsAString& aString, + int32_t aDefault, int32_t aMax) { + ResetIfSet(); + + nsContentUtils::ParseHTMLIntegerResultFlags result; + int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result); + bool nonStrict = false; + if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 1) { + val = aDefault; + nonStrict = true; + } + + if (val > aMax) { + val = aMax; + nonStrict = true; + } + + if ((result & nsContentUtils::eParseHTMLInteger_NonStandard) || + (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput)) { + nonStrict = true; + } + + SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr); +} + +void nsAttrValue::ParseClampedNonNegativeInt(const nsAString& aString, + int32_t aDefault, int32_t aMin, + int32_t aMax) { + ResetIfSet(); + + nsContentUtils::ParseHTMLIntegerResultFlags result; + int32_t val = nsContentUtils::ParseHTMLInteger(aString, &result); + bool nonStrict = + (result & nsContentUtils::eParseHTMLInteger_NonStandard) || + (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput); + + if (result & nsContentUtils::eParseHTMLInteger_ErrorOverflow) { + if (result & nsContentUtils::eParseHTMLInteger_Negative) { + val = aDefault; + } else { + val = aMax; + } + nonStrict = true; + } else if ((result & nsContentUtils::eParseHTMLInteger_Error) || val < 0) { + val = aDefault; + nonStrict = true; + } else if (val < aMin) { + val = aMin; + nonStrict = true; + } else if (val > aMax) { + val = aMax; + nonStrict = true; + } + + SetIntValueAndType(val, eInteger, nonStrict ? &aString : nullptr); +} + +bool nsAttrValue::ParseNonNegativeIntValue(const nsAString& aString) { + ResetIfSet(); + + nsContentUtils::ParseHTMLIntegerResultFlags result; + int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result); + if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal < 0) { + return false; + } + + bool nonStrict = + (result & nsContentUtils::eParseHTMLInteger_NonStandard) || + (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput); + + SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr); + + return true; +} + +bool nsAttrValue::ParsePositiveIntValue(const nsAString& aString) { + ResetIfSet(); + + nsContentUtils::ParseHTMLIntegerResultFlags result; + int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result); + if ((result & nsContentUtils::eParseHTMLInteger_Error) || originalVal <= 0) { + return false; + } + + bool nonStrict = + (result & nsContentUtils::eParseHTMLInteger_NonStandard) || + (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput); + + SetIntValueAndType(originalVal, eInteger, nonStrict ? &aString : nullptr); + + return true; +} + +void nsAttrValue::SetColorValue(nscolor aColor, const nsAString& aString) { + nsStringBuffer* buf = GetStringBuffer(aString).take(); + if (!buf) { + return; + } + + MiscContainer* cont = EnsureEmptyMiscContainer(); + cont->mValue.mColor = aColor; + cont->mType = eColor; + + // Save the literal string we were passed for round-tripping. + cont->SetStringBitsMainThread(reinterpret_cast<uintptr_t>(buf) | eStringBase); +} + +bool nsAttrValue::ParseColor(const nsAString& aString) { + ResetIfSet(); + + // FIXME (partially, at least): HTML5's algorithm says we shouldn't do + // the whitespace compression, trimming, or the test for emptiness. + // (I'm a little skeptical that we shouldn't do the whitespace + // trimming; WebKit also does it.) + nsAutoString colorStr(aString); + colorStr.CompressWhitespace(true, true); + if (colorStr.IsEmpty()) { + return false; + } + + nscolor color; + // No color names begin with a '#'; in standards mode, all acceptable + // numeric colors do. + if (colorStr.First() == '#') { + nsDependentString withoutHash(colorStr.get() + 1, colorStr.Length() - 1); + if (NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) { + SetColorValue(color, aString); + return true; + } + } else if (colorStr.LowerCaseEqualsLiteral("transparent")) { + SetColorValue(NS_RGBA(0, 0, 0, 0), aString); + return true; + } else { + const NS_ConvertUTF16toUTF8 colorNameU8(colorStr); + if (Servo_ColorNameToRgb(&colorNameU8, &color)) { + SetColorValue(color, aString); + return true; + } + } + + // FIXME (maybe): HTML5 says we should handle system colors. This + // means we probably need another storage type, since we'd need to + // handle dynamic changes. However, I think this is a bad idea: + // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-May/026449.html + + // Use NS_LooseHexToRGB as a fallback if nothing above worked. + if (NS_LooseHexToRGB(colorStr, &color)) { + SetColorValue(color, aString); + return true; + } + + return false; +} + +bool nsAttrValue::ParseDoubleValue(const nsAString& aString) { + ResetIfSet(); + + nsresult ec; + double val = PromiseFlatString(aString).ToDouble(&ec); + if (NS_FAILED(ec)) { + return false; + } + + MiscContainer* cont = EnsureEmptyMiscContainer(); + cont->mDoubleValue = val; + cont->mType = eDoubleValue; + nsAutoString serializedFloat; + serializedFloat.AppendFloat(val); + SetMiscAtomOrString(serializedFloat.Equals(aString) ? nullptr : &aString); + return true; +} + +bool nsAttrValue::ParseStyleAttribute(const nsAString& aString, + nsIPrincipal* aMaybeScriptedPrincipal, + nsStyledElement* aElement) { + dom::Document* doc = aElement->OwnerDoc(); + AttributeStyles* attrStyles = doc->GetAttributeStyles(); + NS_ASSERTION(aElement->NodePrincipal() == doc->NodePrincipal(), + "This is unexpected"); + + nsIPrincipal* principal = aMaybeScriptedPrincipal ? aMaybeScriptedPrincipal + : aElement->NodePrincipal(); + RefPtr<URLExtraData> data = aElement->GetURLDataForStyleAttr(principal); + + // If the (immutable) document URI does not match the element's base URI + // (the common case is that they do match) do not cache the rule. This is + // because the results of the CSS parser are dependent on these URIs, and we + // do not want to have to account for the URIs in the hash lookup. + // Similarly, if the triggering principal does not match the node principal, + // do not cache the rule, since the principal will be encoded in any parsed + // URLs in the rule. + const bool cachingAllowed = attrStyles && + doc->GetDocumentURI() == data->BaseURI() && + principal == aElement->NodePrincipal(); + if (cachingAllowed) { + if (MiscContainer* cont = attrStyles->LookupStyleAttr(aString)) { + // Set our MiscContainer to the cached one. + NS_ADDREF(cont); + SetPtrValueAndType(cont, eOtherBase); + return true; + } + } + + RefPtr<DeclarationBlock> decl = + DeclarationBlock::FromCssText(aString, data, doc->GetCompatibilityMode(), + doc->CSSLoader(), StyleCssRuleType::Style); + if (!decl) { + return false; + } + decl->SetAttributeStyles(attrStyles); + SetTo(decl.forget(), &aString); + + if (cachingAllowed) { + MiscContainer* cont = GetMiscContainer(); + cont->Cache(); + } + + return true; +} + +void nsAttrValue::SetMiscAtomOrString(const nsAString* aValue) { + NS_ASSERTION(GetMiscContainer(), "Must have MiscContainer!"); + NS_ASSERTION(!GetMiscContainer()->mStringBits || IsInServoTraversal(), + "Trying to re-set atom or string!"); + if (aValue) { + uint32_t len = aValue->Length(); + // * We're allowing eCSSDeclaration attributes to store empty + // strings as it can be beneficial to store an empty style + // attribute as a parsed rule. + // * We're allowing enumerated values because sometimes the empty + // string corresponds to a particular enumerated value, especially + // for enumerated values that are not limited enumerated. + // Add other types as needed. + NS_ASSERTION(len || Type() == eCSSDeclaration || Type() == eEnum, + "Empty string?"); + MiscContainer* cont = GetMiscContainer(); + + if (len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) { + nsAtom* atom = MOZ_LIKELY(!IsInServoTraversal()) + ? NS_AtomizeMainThread(*aValue).take() + : NS_Atomize(*aValue).take(); + NS_ENSURE_TRUE_VOID(atom); + uintptr_t bits = reinterpret_cast<uintptr_t>(atom) | eAtomBase; + + // In the common case we're not in the servo traversal, and we can just + // set the bits normally. The parallel case requires more care. + if (MOZ_LIKELY(!IsInServoTraversal())) { + cont->SetStringBitsMainThread(bits); + } else if (!cont->mStringBits.compareExchange(0, bits)) { + // We raced with somebody else setting the bits. Release our copy. + atom->Release(); + } + } else { + nsStringBuffer* buffer = GetStringBuffer(*aValue).take(); + NS_ENSURE_TRUE_VOID(buffer); + uintptr_t bits = reinterpret_cast<uintptr_t>(buffer) | eStringBase; + + // In the common case we're not in the servo traversal, and we can just + // set the bits normally. The parallel case requires more care. + if (MOZ_LIKELY(!IsInServoTraversal())) { + cont->SetStringBitsMainThread(bits); + } else if (!cont->mStringBits.compareExchange(0, bits)) { + // We raced with somebody else setting the bits. Release our copy. + buffer->Release(); + } + } + } +} + +void nsAttrValue::ResetMiscAtomOrString() { + MiscContainer* cont = GetMiscContainer(); + bool isString; + if (void* ptr = cont->GetStringOrAtomPtr(isString)) { + if (isString) { + static_cast<nsStringBuffer*>(ptr)->Release(); + } else { + static_cast<nsAtom*>(ptr)->Release(); + } + cont->SetStringBitsMainThread(0); + } +} + +void nsAttrValue::SetSVGType(ValueType aType, const void* aValue, + const nsAString* aSerialized) { + MOZ_ASSERT(IsSVGType(aType), "Not an SVG type"); + + MiscContainer* cont = EnsureEmptyMiscContainer(); + // All SVG types are just pointers to classes so just setting any of them + // will do. We'll lose type-safety but the signature of the calling + // function should ensure we don't get anything unexpected, and once we + // stick aValue in a union we lose type information anyway. + cont->mValue.mSVGLength = static_cast<const SVGAnimatedLength*>(aValue); + cont->mType = aType; + SetMiscAtomOrString(aSerialized); +} + +MiscContainer* nsAttrValue::ClearMiscContainer() { + MiscContainer* cont = nullptr; + if (BaseType() == eOtherBase) { + cont = GetMiscContainer(); + if (cont->IsRefCounted() && cont->mValue.mRefCount > 1) { + // This MiscContainer is shared, we need a new one. + NS_RELEASE(cont); + + cont = AllocMiscContainer(); + SetPtrValueAndType(cont, eOtherBase); + } else { + switch (cont->mType) { + case eCSSDeclaration: { + MOZ_ASSERT(cont->mValue.mRefCount == 1); + cont->Release(); + cont->Evict(); + NS_RELEASE(cont->mValue.mCSSDeclaration); + break; + } + case eShadowParts: { + MOZ_ASSERT(cont->mValue.mRefCount == 1); + cont->Release(); + delete cont->mValue.mShadowParts; + break; + } + case eURL: { + NS_RELEASE(cont->mValue.mURL); + break; + } + case eAtomArray: { + MOZ_ASSERT(cont->mValue.mRefCount == 1); + cont->Release(); + cont->Evict(); + delete cont->mValue.mAtomArray; + break; + } + default: { + break; + } + } + } + ResetMiscAtomOrString(); + } else { + ResetIfSet(); + } + + return cont; +} + +MiscContainer* nsAttrValue::EnsureEmptyMiscContainer() { + MiscContainer* cont = ClearMiscContainer(); + if (cont) { + MOZ_ASSERT(BaseType() == eOtherBase); + ResetMiscAtomOrString(); + cont = GetMiscContainer(); + } else { + cont = AllocMiscContainer(); + SetPtrValueAndType(cont, eOtherBase); + } + + return cont; +} + +already_AddRefed<nsStringBuffer> nsAttrValue::GetStringBuffer( + const nsAString& aValue) const { + uint32_t len = aValue.Length(); + if (!len) { + return nullptr; + } + + RefPtr<nsStringBuffer> buf = nsStringBuffer::FromString(aValue); + if (buf && (buf->StorageSize() / sizeof(char16_t) - 1) == len) { + // We can only reuse the buffer if it's exactly sized, since we rely on + // StorageSize() to get the string length in ToString(). + return buf.forget(); + } + return nsStringBuffer::Create(aValue.Data(), aValue.Length()); +} + +size_t nsAttrValue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + + switch (BaseType()) { + case eStringBase: { + nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr()); + n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0; + break; + } + case eOtherBase: { + MiscContainer* container = GetMiscContainer(); + if (!container) { + break; + } + if (container->IsRefCounted() && container->mValue.mRefCount > 1) { + // We don't report this MiscContainer at all in order to avoid + // twice-reporting it. + // TODO DMD, bug 1027551 - figure out how to report this ref-counted + // object just once. + break; + } + n += aMallocSizeOf(container); + + // We only count the size of the object pointed by otherPtr if it's a + // string. When it's an atom, it's counted separatly. + if (nsStringBuffer* buf = container->GetStoredStringBuffer()) { + n += buf->SizeOfIncludingThisIfUnshared(aMallocSizeOf); + } + + if (Type() == eCSSDeclaration && container->mValue.mCSSDeclaration) { + // TODO: mCSSDeclaration might be owned by another object which + // would make us count them twice, bug 677493. + // Bug 1281964: For DeclarationBlock if we do measure we'll + // need a way to call the Servo heap_size_of function. + // n += container->mCSSDeclaration->SizeOfIncludingThis(aMallocSizeOf); + } else if (Type() == eAtomArray && container->mValue.mAtomArray) { + // Don't measure each nsAtom, they are measured separatly. + n += container->mValue.mAtomArray->mArray.ShallowSizeOfIncludingThis( + aMallocSizeOf); + } + break; + } + case eAtomBase: // Atoms are counted separately. + case eIntegerBase: // The value is in mBits, nothing to do. + break; + } + + return n; +} |