447 lines
13 KiB
C++
447 lines
13 KiB
C++
/* 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/. */
|
|
|
|
/*
|
|
* Storage of the children and attributes of a DOM node; storage for
|
|
* the two is unified to minimize footprint.
|
|
*/
|
|
|
|
#include "AttrArray.h"
|
|
|
|
#include "mozilla/AttributeStyles.h"
|
|
#include "mozilla/CheckedInt.h"
|
|
#include "mozilla/MathAlgorithms.h"
|
|
#include "mozilla/MemoryReporting.h"
|
|
#include "mozilla/ServoBindings.h"
|
|
#include "nsContentUtils.h" // nsAutoScriptBlocker
|
|
#include "nsString.h"
|
|
#include "nsUnicharUtils.h"
|
|
|
|
using mozilla::CheckedUint32;
|
|
|
|
AttrArray::Impl::~Impl() {
|
|
for (InternalAttr& attr : Attrs()) {
|
|
attr.~InternalAttr();
|
|
}
|
|
if (auto* decl = GetMappedDeclarationBlock()) {
|
|
Servo_DeclarationBlock_Release(decl);
|
|
mMappedAttributeBits = 0;
|
|
}
|
|
}
|
|
|
|
void AttrArray::SetMappedDeclarationBlock(
|
|
already_AddRefed<mozilla::StyleLockedDeclarationBlock> aBlock) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(HasImpl());
|
|
MOZ_ASSERT(IsPendingMappedAttributeEvaluation());
|
|
if (auto* decl = GetMappedDeclarationBlock()) {
|
|
Servo_DeclarationBlock_Release(decl);
|
|
}
|
|
GetImpl()->mMappedAttributeBits = reinterpret_cast<uintptr_t>(aBlock.take());
|
|
MOZ_ASSERT(!IsPendingMappedAttributeEvaluation());
|
|
}
|
|
|
|
const nsAttrValue* AttrArray::GetAttr(const nsAtom* aLocalName) const {
|
|
NS_ASSERTION(aLocalName, "Must have attr name");
|
|
for (const InternalAttr& attr : Attrs()) {
|
|
if (attr.mName.Equals(aLocalName)) {
|
|
return &attr.mValue;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const nsAttrValue* AttrArray::GetAttr(const nsAtom* aLocalName,
|
|
int32_t aNamespaceID) const {
|
|
NS_ASSERTION(aLocalName, "Must have attr name");
|
|
NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown, "Must have namespace");
|
|
if (aNamespaceID == kNameSpaceID_None) {
|
|
// This should be the common case so lets use the optimized loop
|
|
return GetAttr(aLocalName);
|
|
}
|
|
for (const InternalAttr& attr : Attrs()) {
|
|
if (attr.mName.Equals(aLocalName, aNamespaceID)) {
|
|
return &attr.mValue;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const nsAttrValue* AttrArray::GetAttr(const nsAString& aLocalName) const {
|
|
for (const InternalAttr& attr : Attrs()) {
|
|
if (attr.mName.Equals(aLocalName)) {
|
|
return &attr.mValue;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const nsAttrValue* AttrArray::GetAttr(const nsAString& aName,
|
|
nsCaseTreatment aCaseSensitive) const {
|
|
// Check whether someone is being silly and passing non-lowercase
|
|
// attr names.
|
|
if (aCaseSensitive == eIgnoreCase &&
|
|
nsContentUtils::StringContainsASCIIUpper(aName)) {
|
|
// Try again with a lowercased name, but make sure we can't reenter this
|
|
// block by passing eCaseSensitive for aCaseSensitive.
|
|
nsAutoString lowercase;
|
|
nsContentUtils::ASCIIToLower(aName, lowercase);
|
|
return GetAttr(lowercase, eCaseMatters);
|
|
}
|
|
|
|
for (const InternalAttr& attr : Attrs()) {
|
|
if (attr.mName.QualifiedNameEquals(aName)) {
|
|
return &attr.mValue;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const nsAttrValue* AttrArray::AttrAt(uint32_t aPos) const {
|
|
NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
|
|
return &GetImpl()->Attrs()[aPos].mValue;
|
|
}
|
|
|
|
template <typename Name>
|
|
inline nsresult AttrArray::AddNewAttribute(Name* aName, nsAttrValue& aValue) {
|
|
MOZ_ASSERT(!HasImpl() || GetImpl()->mCapacity >= GetImpl()->mAttrCount);
|
|
if (!HasImpl() || GetImpl()->mCapacity == GetImpl()->mAttrCount) {
|
|
if (!GrowBy(1)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
InternalAttr& attr = mImpl->mBuffer[mImpl->mAttrCount++];
|
|
new (&attr.mName) nsAttrName(aName);
|
|
new (&attr.mValue) nsAttrValue();
|
|
attr.mValue.SwapValueWith(aValue);
|
|
return NS_OK;
|
|
}
|
|
|
|
const nsAttrValue* AttrArray::AddNewAttributeAssumeAvailableSlot(
|
|
RefPtr<nsAtom>& aName, nsAttrValue& aValue) {
|
|
MOZ_ASSERT(HasImpl());
|
|
MOZ_ASSERT(GetImpl()->mAttrCount < GetImpl()->mCapacity);
|
|
InternalAttr& attr = mImpl->mBuffer[mImpl->mAttrCount++];
|
|
new (&attr.mName) nsAttrName(aName.forget());
|
|
new (&attr.mValue) nsAttrValue();
|
|
attr.mValue.SwapValueWith(aValue);
|
|
return &attr.mValue;
|
|
}
|
|
|
|
nsresult AttrArray::SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue,
|
|
bool* aHadValue,
|
|
mozilla::dom::IsKnownNewAttr aIsKnownNew) {
|
|
*aHadValue = false;
|
|
|
|
if (aIsKnownNew == mozilla::dom::IsKnownNewAttr::No) {
|
|
for (InternalAttr& attr : Attrs()) {
|
|
if (attr.mName.Equals(aLocalName)) {
|
|
attr.mValue.SwapValueWith(aValue);
|
|
*aHadValue = true;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
} else {
|
|
MOZ_ASSERT(IndexOfAttr(aLocalName) == -1,
|
|
"Caller asserted attribute is new but it already exists");
|
|
}
|
|
|
|
return AddNewAttribute(aLocalName, aValue);
|
|
}
|
|
|
|
nsresult AttrArray::SetAndSwapAttr(mozilla::dom::NodeInfo* aName,
|
|
nsAttrValue& aValue, bool* aHadValue,
|
|
mozilla::dom::IsKnownNewAttr aIsKnownNew) {
|
|
int32_t namespaceID = aName->NamespaceID();
|
|
nsAtom* localName = aName->NameAtom();
|
|
if (namespaceID == kNameSpaceID_None) {
|
|
return SetAndSwapAttr(localName, aValue, aHadValue, aIsKnownNew);
|
|
}
|
|
|
|
*aHadValue = false;
|
|
if (aIsKnownNew == mozilla::dom::IsKnownNewAttr::No) {
|
|
for (InternalAttr& attr : Attrs()) {
|
|
if (attr.mName.Equals(localName, namespaceID)) {
|
|
attr.mName.SetTo(aName);
|
|
attr.mValue.SwapValueWith(aValue);
|
|
*aHadValue = true;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
} else {
|
|
MOZ_ASSERT(IndexOfAttr(localName, namespaceID) == -1,
|
|
"Caller asserted attribute is new but it already exists");
|
|
}
|
|
|
|
return AddNewAttribute(aName, aValue);
|
|
}
|
|
|
|
nsresult AttrArray::RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue) {
|
|
NS_ASSERTION(aPos < AttrCount(), "out-of-bounds");
|
|
|
|
Impl* impl = GetImpl();
|
|
impl->mBuffer[aPos].mValue.SwapValueWith(aValue);
|
|
impl->mBuffer[aPos].~InternalAttr();
|
|
|
|
// InternalAttr are not trivially copyable *but* we manually called the
|
|
// destructor so the memmove should be ok.
|
|
memmove((void*)(impl->mBuffer + aPos), impl->mBuffer + aPos + 1,
|
|
(impl->mAttrCount - aPos - 1) * sizeof(InternalAttr));
|
|
|
|
--impl->mAttrCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
mozilla::dom::BorrowedAttrInfo AttrArray::AttrInfoAt(uint32_t aPos) const {
|
|
NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
|
|
const Impl* impl = GetImpl();
|
|
return BorrowedAttrInfo(&impl->mBuffer[aPos].mName,
|
|
&impl->mBuffer[aPos].mValue);
|
|
}
|
|
|
|
const nsAttrName* AttrArray::AttrNameAt(uint32_t aPos) const {
|
|
NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
|
|
return &GetImpl()->mBuffer[aPos].mName;
|
|
}
|
|
|
|
[[nodiscard]] bool AttrArray::GetSafeAttrNameAt(
|
|
uint32_t aPos, const nsAttrName** aResult) const {
|
|
if (aPos >= AttrCount()) {
|
|
return false;
|
|
}
|
|
*aResult = &GetImpl()->mBuffer[aPos].mName;
|
|
return true;
|
|
}
|
|
|
|
const nsAttrName* AttrArray::GetSafeAttrNameAt(uint32_t aPos) const {
|
|
const nsAttrName* name;
|
|
if (!GetSafeAttrNameAt(aPos, &name)) {
|
|
MOZ_CRASH("aPos out of bounds");
|
|
}
|
|
return name;
|
|
}
|
|
|
|
const nsAttrName* AttrArray::GetExistingAttrNameFromQName(
|
|
const nsAString& aName, RefPtr<nsAtom>* aOutAtom) const {
|
|
if (aOutAtom) {
|
|
*aOutAtom = nullptr;
|
|
}
|
|
if (!HasAttrs()) {
|
|
return nullptr;
|
|
}
|
|
RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(aName);
|
|
for (const InternalAttr& attr : Attrs()) {
|
|
// Equals(nsAtom*) compares mBits; for NodeInfo-typed entries mBits has the
|
|
// tag bit set and can never equal a clean atom pointer, so this is safe
|
|
// for both kinds without branching on IsAtom().
|
|
if (attr.mName.Equals(nameAtom.get())) {
|
|
return &attr.mName;
|
|
}
|
|
if (!attr.mName.IsAtom() && attr.mName.QualifiedNameEquals(aName)) {
|
|
return &attr.mName;
|
|
}
|
|
}
|
|
if (aOutAtom) {
|
|
*aOutAtom = nameAtom.forget();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int32_t AttrArray::IndexOfAttr(const nsAtom* aLocalName) const {
|
|
int32_t i = 0;
|
|
for (const InternalAttr& attr : Attrs()) {
|
|
if (attr.mName.Equals(aLocalName)) {
|
|
return i;
|
|
}
|
|
++i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int32_t AttrArray::IndexOfAttr(const nsAtom* aLocalName,
|
|
int32_t aNamespaceID) const {
|
|
if (aNamespaceID == kNameSpaceID_None) {
|
|
// This should be the common case so lets use the optimized loop
|
|
return IndexOfAttr(aLocalName);
|
|
}
|
|
int32_t i = 0;
|
|
for (const InternalAttr& attr : Attrs()) {
|
|
if (attr.mName.Equals(aLocalName, aNamespaceID)) {
|
|
return i;
|
|
}
|
|
++i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void AttrArray::Compact() {
|
|
if (!HasImpl()) {
|
|
return;
|
|
}
|
|
|
|
Impl* impl = GetImpl();
|
|
if (!impl->mAttrCount && !impl->mMappedAttributeBits) {
|
|
Clear();
|
|
return;
|
|
}
|
|
|
|
// Nothing to do.
|
|
if (impl->mAttrCount == impl->mCapacity) {
|
|
return;
|
|
}
|
|
|
|
// Extract the real pointer for realloc
|
|
Impl* oldImpl = mImpl.release();
|
|
|
|
Impl* newImpl = static_cast<Impl*>(
|
|
realloc(oldImpl, Impl::AllocationSizeForAttributes(oldImpl->mAttrCount)));
|
|
if (!newImpl) {
|
|
SetImpl(oldImpl);
|
|
return;
|
|
}
|
|
newImpl->mCapacity = newImpl->mAttrCount;
|
|
SetImpl(newImpl);
|
|
}
|
|
|
|
nsresult AttrArray::EnsureCapacityToClone(const AttrArray& aOther) {
|
|
MOZ_ASSERT(!HasImpl(),
|
|
"AttrArray::EnsureCapacityToClone requires the array be empty "
|
|
"when called");
|
|
|
|
uint32_t attrCount = aOther.AttrCount();
|
|
if (!attrCount) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// No need to use a CheckedUint32 because we are cloning. We know that we
|
|
// have already allocated an AttrArray of this size.
|
|
Impl* impl =
|
|
static_cast<Impl*>(malloc(Impl::AllocationSizeForAttributes(attrCount)));
|
|
NS_ENSURE_TRUE(impl, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
impl->mMappedAttributeBits = 0;
|
|
impl->mCapacity = attrCount;
|
|
impl->mAttrCount = 0;
|
|
impl->mSubtreeBloomFilter = aOther.GetSubtreeBloomFilter();
|
|
SetImpl(impl);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool AttrArray::GrowBy(uint32_t aGrowSize) {
|
|
const uint32_t kLinearThreshold = 16;
|
|
const uint32_t kLinearGrowSize = 4;
|
|
|
|
CheckedUint32 capacity = HasImpl() ? GetImpl()->mCapacity : 0;
|
|
CheckedUint32 minCapacity = capacity;
|
|
minCapacity += aGrowSize;
|
|
if (!minCapacity.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
if (capacity.value() <= kLinearThreshold) {
|
|
do {
|
|
capacity += kLinearGrowSize;
|
|
if (!capacity.isValid()) {
|
|
return false;
|
|
}
|
|
} while (capacity.value() < minCapacity.value());
|
|
} else {
|
|
uint32_t shift = mozilla::CeilingLog2(minCapacity.value());
|
|
if (shift >= 32) {
|
|
return false;
|
|
}
|
|
capacity = 1u << shift;
|
|
}
|
|
|
|
return GrowTo(capacity.value());
|
|
}
|
|
|
|
bool AttrArray::GrowTo(uint32_t aCapacity) {
|
|
uint32_t oldCapacity = HasImpl() ? GetImpl()->mCapacity : 0;
|
|
if (aCapacity <= oldCapacity) {
|
|
return true;
|
|
}
|
|
|
|
CheckedUint32 sizeInBytes = aCapacity;
|
|
sizeInBytes *= sizeof(InternalAttr);
|
|
if (!sizeInBytes.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
sizeInBytes += sizeof(Impl);
|
|
if (!sizeInBytes.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(sizeInBytes.value() ==
|
|
Impl::AllocationSizeForAttributes(aCapacity));
|
|
|
|
const bool needToInitialize = !HasImpl();
|
|
uint64_t oldBloom = 0xFFFFFFFFFFFFFFFFULL;
|
|
Impl* oldImpl = nullptr;
|
|
|
|
if (HasImpl()) {
|
|
// We have a real Impl pointer, extract it for realloc
|
|
oldImpl = mImpl.release();
|
|
} else if (HasTaggedBloom()) {
|
|
// Preserve bloom filter from the tagged value
|
|
oldBloom = GetTaggedBloom();
|
|
}
|
|
|
|
Impl* newImpl = static_cast<Impl*>(realloc(oldImpl, sizeInBytes.value()));
|
|
if (!newImpl) {
|
|
if (oldImpl) {
|
|
SetImpl(oldImpl);
|
|
} else if (HasTaggedBloom()) {
|
|
SetTaggedBloom(oldBloom);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Set initial counts if we didn't have a buffer before
|
|
if (needToInitialize) {
|
|
newImpl->mMappedAttributeBits = 0;
|
|
newImpl->mAttrCount = 0;
|
|
newImpl->mSubtreeBloomFilter = oldBloom;
|
|
}
|
|
|
|
newImpl->mCapacity = aCapacity;
|
|
SetImpl(newImpl);
|
|
return true;
|
|
}
|
|
|
|
size_t AttrArray::SizeOfExcludingThis(
|
|
mozilla::MallocSizeOf aMallocSizeOf) const {
|
|
if (!HasImpl()) {
|
|
return 0;
|
|
}
|
|
size_t n = aMallocSizeOf(GetImpl());
|
|
for (const InternalAttr& attr : Attrs()) {
|
|
n += attr.mValue.SizeOfExcludingThis(aMallocSizeOf);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int32_t AttrArray::FindAttrValueIn(int32_t aNameSpaceID, const nsAtom* aName,
|
|
AttrValuesArray* aValues,
|
|
nsCaseTreatment aCaseSensitive) const {
|
|
NS_ASSERTION(aName, "Must have attr name");
|
|
NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown, "Must have namespace");
|
|
NS_ASSERTION(aValues, "Null value array");
|
|
|
|
const nsAttrValue* val = GetAttr(aName, aNameSpaceID);
|
|
if (val) {
|
|
for (int32_t i = 0; aValues[i]; ++i) {
|
|
if (val->Equals(aValues[i], aCaseSensitive)) {
|
|
return i;
|
|
}
|
|
}
|
|
return ATTR_VALUE_NO_MATCH;
|
|
}
|
|
return ATTR_MISSING;
|
|
}
|