Backed out changeset 9d8b4c0b99ed (bug 1945683) for causing btime failures. CLOSED...
[gecko.git] / dom / base / AttrArray.cpp
blob929ec8da8c3749c0baac920ecd0f15d12b18031b
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 * Storage of the children and attributes of a DOM node; storage for
9 * the two is unified to minimize footprint.
12 #include "AttrArray.h"
14 #include "mozilla/AttributeStyles.h"
15 #include "mozilla/CheckedInt.h"
16 #include "mozilla/MathAlgorithms.h"
17 #include "mozilla/MemoryReporting.h"
18 #include "mozilla/ServoBindings.h"
20 #include "nsString.h"
21 #include "nsUnicharUtils.h"
22 #include "nsContentUtils.h" // nsAutoScriptBlocker
24 using mozilla::CheckedUint32;
26 AttrArray::Impl::~Impl() {
27 for (InternalAttr& attr : Attrs()) {
28 attr.~InternalAttr();
30 if (auto* decl = GetMappedDeclarationBlock()) {
31 Servo_DeclarationBlock_Release(decl);
32 mMappedAttributeBits = 0;
36 void AttrArray::SetMappedDeclarationBlock(
37 already_AddRefed<mozilla::StyleLockedDeclarationBlock> aBlock) {
38 MOZ_ASSERT(NS_IsMainThread());
39 MOZ_ASSERT(mImpl);
40 MOZ_ASSERT(IsPendingMappedAttributeEvaluation());
41 if (auto* decl = GetMappedDeclarationBlock()) {
42 Servo_DeclarationBlock_Release(decl);
44 mImpl->mMappedAttributeBits = reinterpret_cast<uintptr_t>(aBlock.take());
45 MOZ_ASSERT(!IsPendingMappedAttributeEvaluation());
48 const nsAttrValue* AttrArray::GetAttr(const nsAtom* aLocalName) const {
49 NS_ASSERTION(aLocalName, "Must have attr name");
50 for (const InternalAttr& attr : Attrs()) {
51 if (attr.mName.Equals(aLocalName)) {
52 return &attr.mValue;
55 return nullptr;
58 const nsAttrValue* AttrArray::GetAttr(const nsAtom* aLocalName,
59 int32_t aNamespaceID) const {
60 NS_ASSERTION(aLocalName, "Must have attr name");
61 NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown, "Must have namespace");
62 if (aNamespaceID == kNameSpaceID_None) {
63 // This should be the common case so lets use the optimized loop
64 return GetAttr(aLocalName);
66 for (const InternalAttr& attr : Attrs()) {
67 if (attr.mName.Equals(aLocalName, aNamespaceID)) {
68 return &attr.mValue;
71 return nullptr;
74 const nsAttrValue* AttrArray::GetAttr(const nsAString& aLocalName) const {
75 for (const InternalAttr& attr : Attrs()) {
76 if (attr.mName.Equals(aLocalName)) {
77 return &attr.mValue;
80 return nullptr;
83 const nsAttrValue* AttrArray::GetAttr(const nsAString& aName,
84 nsCaseTreatment aCaseSensitive) const {
85 // Check whether someone is being silly and passing non-lowercase
86 // attr names.
87 if (aCaseSensitive == eIgnoreCase &&
88 nsContentUtils::StringContainsASCIIUpper(aName)) {
89 // Try again with a lowercased name, but make sure we can't reenter this
90 // block by passing eCaseSensitive for aCaseSensitive.
91 nsAutoString lowercase;
92 nsContentUtils::ASCIIToLower(aName, lowercase);
93 return GetAttr(lowercase, eCaseMatters);
96 for (const InternalAttr& attr : Attrs()) {
97 if (attr.mName.QualifiedNameEquals(aName)) {
98 return &attr.mValue;
102 return nullptr;
105 const nsAttrValue* AttrArray::AttrAt(uint32_t aPos) const {
106 NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
107 return &mImpl->Attrs()[aPos].mValue;
110 template <typename Name>
111 inline nsresult AttrArray::AddNewAttribute(Name* aName, nsAttrValue& aValue) {
112 MOZ_ASSERT(!mImpl || mImpl->mCapacity >= mImpl->mAttrCount);
113 if (!mImpl || mImpl->mCapacity == mImpl->mAttrCount) {
114 if (!GrowBy(1)) {
115 return NS_ERROR_OUT_OF_MEMORY;
119 InternalAttr& attr = mImpl->mBuffer[mImpl->mAttrCount++];
120 new (&attr.mName) nsAttrName(aName);
121 new (&attr.mValue) nsAttrValue();
122 attr.mValue.SwapValueWith(aValue);
123 return NS_OK;
126 nsresult AttrArray::SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue,
127 bool* aHadValue) {
128 *aHadValue = false;
130 for (InternalAttr& attr : Attrs()) {
131 if (attr.mName.Equals(aLocalName)) {
132 attr.mValue.SwapValueWith(aValue);
133 *aHadValue = true;
134 return NS_OK;
138 return AddNewAttribute(aLocalName, aValue);
141 nsresult AttrArray::SetAndSwapAttr(mozilla::dom::NodeInfo* aName,
142 nsAttrValue& aValue, bool* aHadValue) {
143 int32_t namespaceID = aName->NamespaceID();
144 nsAtom* localName = aName->NameAtom();
145 if (namespaceID == kNameSpaceID_None) {
146 return SetAndSwapAttr(localName, aValue, aHadValue);
149 *aHadValue = false;
150 for (InternalAttr& attr : Attrs()) {
151 if (attr.mName.Equals(localName, namespaceID)) {
152 attr.mName.SetTo(aName);
153 attr.mValue.SwapValueWith(aValue);
154 *aHadValue = true;
155 return NS_OK;
159 return AddNewAttribute(aName, aValue);
162 nsresult AttrArray::RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue) {
163 NS_ASSERTION(aPos < AttrCount(), "out-of-bounds");
165 mImpl->mBuffer[aPos].mValue.SwapValueWith(aValue);
166 mImpl->mBuffer[aPos].~InternalAttr();
168 // InternalAttr are not trivially copyable *but* we manually called the
169 // destructor so the memmove should be ok.
170 memmove((void*)(mImpl->mBuffer + aPos), mImpl->mBuffer + aPos + 1,
171 (mImpl->mAttrCount - aPos - 1) * sizeof(InternalAttr));
173 --mImpl->mAttrCount;
174 return NS_OK;
177 mozilla::dom::BorrowedAttrInfo AttrArray::AttrInfoAt(uint32_t aPos) const {
178 NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
179 InternalAttr& attr = mImpl->mBuffer[aPos];
180 return BorrowedAttrInfo(&attr.mName, &attr.mValue);
183 const nsAttrName* AttrArray::AttrNameAt(uint32_t aPos) const {
184 NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
185 return &mImpl->mBuffer[aPos].mName;
188 const nsAttrName* AttrArray::GetSafeAttrNameAt(uint32_t aPos) const {
189 if (aPos >= AttrCount()) {
190 return nullptr;
192 return &mImpl->mBuffer[aPos].mName;
195 const nsAttrName* AttrArray::GetExistingAttrNameFromQName(
196 const nsAString& aName) const {
197 for (const InternalAttr& attr : Attrs()) {
198 if (attr.mName.QualifiedNameEquals(aName)) {
199 return &attr.mName;
202 return nullptr;
205 int32_t AttrArray::IndexOfAttr(const nsAtom* aLocalName) const {
206 int32_t i = 0;
207 for (const InternalAttr& attr : Attrs()) {
208 if (attr.mName.Equals(aLocalName)) {
209 return i;
211 ++i;
213 return -1;
216 int32_t AttrArray::IndexOfAttr(const nsAtom* aLocalName,
217 int32_t aNamespaceID) const {
218 if (aNamespaceID == kNameSpaceID_None) {
219 // This should be the common case so lets use the optimized loop
220 return IndexOfAttr(aLocalName);
222 int32_t i = 0;
223 for (const InternalAttr& attr : Attrs()) {
224 if (attr.mName.Equals(aLocalName, aNamespaceID)) {
225 return i;
227 ++i;
229 return -1;
232 void AttrArray::Compact() {
233 if (!mImpl) {
234 return;
237 if (!mImpl->mAttrCount && !mImpl->mMappedAttributeBits) {
238 mImpl.reset();
239 return;
242 // Nothing to do.
243 if (mImpl->mAttrCount == mImpl->mCapacity) {
244 return;
247 Impl* oldImpl = mImpl.release();
248 Impl* impl = static_cast<Impl*>(
249 realloc(oldImpl, Impl::AllocationSizeForAttributes(oldImpl->mAttrCount)));
250 if (!impl) {
251 mImpl.reset(oldImpl);
252 return;
254 impl->mCapacity = impl->mAttrCount;
255 mImpl.reset(impl);
258 nsresult AttrArray::EnsureCapacityToClone(const AttrArray& aOther) {
259 MOZ_ASSERT(!mImpl,
260 "AttrArray::EnsureCapacityToClone requires the array be empty "
261 "when called");
263 uint32_t attrCount = aOther.AttrCount();
264 if (!attrCount) {
265 return NS_OK;
268 // No need to use a CheckedUint32 because we are cloning. We know that we
269 // have already allocated an AttrArray of this size.
270 mImpl.reset(
271 static_cast<Impl*>(malloc(Impl::AllocationSizeForAttributes(attrCount))));
272 NS_ENSURE_TRUE(mImpl, NS_ERROR_OUT_OF_MEMORY);
274 mImpl->mMappedAttributeBits = 0;
275 mImpl->mCapacity = attrCount;
276 mImpl->mAttrCount = 0;
278 return NS_OK;
281 bool AttrArray::GrowBy(uint32_t aGrowSize) {
282 const uint32_t kLinearThreshold = 16;
283 const uint32_t kLinearGrowSize = 4;
285 CheckedUint32 capacity = mImpl ? mImpl->mCapacity : 0;
286 CheckedUint32 minCapacity = capacity;
287 minCapacity += aGrowSize;
288 if (!minCapacity.isValid()) {
289 return false;
292 if (capacity.value() <= kLinearThreshold) {
293 do {
294 capacity += kLinearGrowSize;
295 if (!capacity.isValid()) {
296 return false;
298 } while (capacity.value() < minCapacity.value());
299 } else {
300 uint32_t shift = mozilla::CeilingLog2(minCapacity.value());
301 if (shift >= 32) {
302 return false;
304 capacity = 1u << shift;
307 return GrowTo(capacity.value());
310 bool AttrArray::GrowTo(uint32_t aCapacity) {
311 uint32_t oldCapacity = mImpl ? mImpl->mCapacity : 0;
312 if (aCapacity <= oldCapacity) {
313 return true;
316 CheckedUint32 sizeInBytes = aCapacity;
317 sizeInBytes *= sizeof(InternalAttr);
318 if (!sizeInBytes.isValid()) {
319 return false;
322 sizeInBytes += sizeof(Impl);
323 if (!sizeInBytes.isValid()) {
324 return false;
327 MOZ_ASSERT(sizeInBytes.value() ==
328 Impl::AllocationSizeForAttributes(aCapacity));
330 const bool needToInitialize = !mImpl;
331 Impl* oldImpl = mImpl.release();
332 Impl* newImpl = static_cast<Impl*>(realloc(oldImpl, sizeInBytes.value()));
333 if (!newImpl) {
334 mImpl.reset(oldImpl);
335 return false;
338 mImpl.reset(newImpl);
340 // Set initial counts if we didn't have a buffer before
341 if (needToInitialize) {
342 mImpl->mMappedAttributeBits = 0;
343 mImpl->mAttrCount = 0;
346 mImpl->mCapacity = aCapacity;
347 return true;
350 size_t AttrArray::SizeOfExcludingThis(
351 mozilla::MallocSizeOf aMallocSizeOf) const {
352 if (!mImpl) {
353 return 0;
355 size_t n = aMallocSizeOf(mImpl.get());
356 for (const InternalAttr& attr : Attrs()) {
357 n += attr.mValue.SizeOfExcludingThis(aMallocSizeOf);
359 return n;
362 int32_t AttrArray::FindAttrValueIn(int32_t aNameSpaceID, const nsAtom* aName,
363 AttrValuesArray* aValues,
364 nsCaseTreatment aCaseSensitive) const {
365 NS_ASSERTION(aName, "Must have attr name");
366 NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown, "Must have namespace");
367 NS_ASSERTION(aValues, "Null value array");
369 const nsAttrValue* val = GetAttr(aName, aNameSpaceID);
370 if (val) {
371 for (int32_t i = 0; aValues[i]; ++i) {
372 if (val->Equals(aValues[i], aCaseSensitive)) {
373 return i;
376 return ATTR_VALUE_NO_MATCH;
378 return ATTR_MISSING;