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/. */
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"
21 #include "nsUnicharUtils.h"
22 #include "nsContentUtils.h" // nsAutoScriptBlocker
24 using mozilla::CheckedUint32
;
26 AttrArray::Impl::~Impl() {
27 for (InternalAttr
& attr
: Attrs()) {
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());
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
)) {
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
)) {
74 const nsAttrValue
* AttrArray::GetAttr(const nsAString
& aLocalName
) const {
75 for (const InternalAttr
& attr
: Attrs()) {
76 if (attr
.mName
.Equals(aLocalName
)) {
83 const nsAttrValue
* AttrArray::GetAttr(const nsAString
& aName
,
84 nsCaseTreatment aCaseSensitive
) const {
85 // Check whether someone is being silly and passing non-lowercase
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
)) {
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
) {
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
);
126 nsresult
AttrArray::SetAndSwapAttr(nsAtom
* aLocalName
, nsAttrValue
& aValue
,
130 for (InternalAttr
& attr
: Attrs()) {
131 if (attr
.mName
.Equals(aLocalName
)) {
132 attr
.mValue
.SwapValueWith(aValue
);
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
);
150 for (InternalAttr
& attr
: Attrs()) {
151 if (attr
.mName
.Equals(localName
, namespaceID
)) {
152 attr
.mName
.SetTo(aName
);
153 attr
.mValue
.SwapValueWith(aValue
);
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 memmove(mImpl
->mBuffer
+ aPos
, mImpl
->mBuffer
+ aPos
+ 1,
169 (mImpl
->mAttrCount
- aPos
- 1) * sizeof(InternalAttr
));
175 mozilla::dom::BorrowedAttrInfo
AttrArray::AttrInfoAt(uint32_t aPos
) const {
176 NS_ASSERTION(aPos
< AttrCount(), "out-of-bounds access in AttrArray");
177 InternalAttr
& attr
= mImpl
->mBuffer
[aPos
];
178 return BorrowedAttrInfo(&attr
.mName
, &attr
.mValue
);
181 const nsAttrName
* AttrArray::AttrNameAt(uint32_t aPos
) const {
182 NS_ASSERTION(aPos
< AttrCount(), "out-of-bounds access in AttrArray");
183 return &mImpl
->mBuffer
[aPos
].mName
;
186 const nsAttrName
* AttrArray::GetSafeAttrNameAt(uint32_t aPos
) const {
187 if (aPos
>= AttrCount()) {
190 return &mImpl
->mBuffer
[aPos
].mName
;
193 const nsAttrName
* AttrArray::GetExistingAttrNameFromQName(
194 const nsAString
& aName
) const {
195 for (const InternalAttr
& attr
: Attrs()) {
196 if (attr
.mName
.QualifiedNameEquals(aName
)) {
203 int32_t AttrArray::IndexOfAttr(const nsAtom
* aLocalName
) const {
205 for (const InternalAttr
& attr
: Attrs()) {
206 if (attr
.mName
.Equals(aLocalName
)) {
214 int32_t AttrArray::IndexOfAttr(const nsAtom
* aLocalName
,
215 int32_t aNamespaceID
) const {
216 if (aNamespaceID
== kNameSpaceID_None
) {
217 // This should be the common case so lets use the optimized loop
218 return IndexOfAttr(aLocalName
);
221 for (const InternalAttr
& attr
: Attrs()) {
222 if (attr
.mName
.Equals(aLocalName
, aNamespaceID
)) {
230 void AttrArray::Compact() {
235 if (!mImpl
->mAttrCount
&& !mImpl
->mMappedAttributeBits
) {
241 if (mImpl
->mAttrCount
== mImpl
->mCapacity
) {
245 Impl
* oldImpl
= mImpl
.release();
246 Impl
* impl
= static_cast<Impl
*>(
247 realloc(oldImpl
, Impl::AllocationSizeForAttributes(oldImpl
->mAttrCount
)));
249 mImpl
.reset(oldImpl
);
252 impl
->mCapacity
= impl
->mAttrCount
;
256 nsresult
AttrArray::EnsureCapacityToClone(const AttrArray
& aOther
) {
258 "AttrArray::EnsureCapacityToClone requires the array be empty "
261 uint32_t attrCount
= aOther
.AttrCount();
266 // No need to use a CheckedUint32 because we are cloning. We know that we
267 // have already allocated an AttrArray of this size.
269 static_cast<Impl
*>(malloc(Impl::AllocationSizeForAttributes(attrCount
))));
270 NS_ENSURE_TRUE(mImpl
, NS_ERROR_OUT_OF_MEMORY
);
272 mImpl
->mMappedAttributeBits
= 0;
273 mImpl
->mCapacity
= attrCount
;
274 mImpl
->mAttrCount
= 0;
279 bool AttrArray::GrowBy(uint32_t aGrowSize
) {
280 const uint32_t kLinearThreshold
= 16;
281 const uint32_t kLinearGrowSize
= 4;
283 CheckedUint32 capacity
= mImpl
? mImpl
->mCapacity
: 0;
284 CheckedUint32 minCapacity
= capacity
;
285 minCapacity
+= aGrowSize
;
286 if (!minCapacity
.isValid()) {
290 if (capacity
.value() <= kLinearThreshold
) {
292 capacity
+= kLinearGrowSize
;
293 if (!capacity
.isValid()) {
296 } while (capacity
.value() < minCapacity
.value());
298 uint32_t shift
= mozilla::CeilingLog2(minCapacity
.value());
302 capacity
= 1u << shift
;
305 return GrowTo(capacity
.value());
308 bool AttrArray::GrowTo(uint32_t aCapacity
) {
309 uint32_t oldCapacity
= mImpl
? mImpl
->mCapacity
: 0;
310 if (aCapacity
<= oldCapacity
) {
314 CheckedUint32 sizeInBytes
= aCapacity
;
315 sizeInBytes
*= sizeof(InternalAttr
);
316 if (!sizeInBytes
.isValid()) {
320 sizeInBytes
+= sizeof(Impl
);
321 if (!sizeInBytes
.isValid()) {
325 MOZ_ASSERT(sizeInBytes
.value() ==
326 Impl::AllocationSizeForAttributes(aCapacity
));
328 const bool needToInitialize
= !mImpl
;
329 Impl
* oldImpl
= mImpl
.release();
330 Impl
* newImpl
= static_cast<Impl
*>(realloc(oldImpl
, sizeInBytes
.value()));
332 mImpl
.reset(oldImpl
);
336 mImpl
.reset(newImpl
);
338 // Set initial counts if we didn't have a buffer before
339 if (needToInitialize
) {
340 mImpl
->mMappedAttributeBits
= 0;
341 mImpl
->mAttrCount
= 0;
344 mImpl
->mCapacity
= aCapacity
;
348 size_t AttrArray::SizeOfExcludingThis(
349 mozilla::MallocSizeOf aMallocSizeOf
) const {
353 size_t n
= aMallocSizeOf(mImpl
.get());
354 for (const InternalAttr
& attr
: Attrs()) {
355 n
+= attr
.mValue
.SizeOfExcludingThis(aMallocSizeOf
);
360 int32_t AttrArray::FindAttrValueIn(int32_t aNameSpaceID
, const nsAtom
* aName
,
361 AttrValuesArray
* aValues
,
362 nsCaseTreatment aCaseSensitive
) const {
363 NS_ASSERTION(aName
, "Must have attr name");
364 NS_ASSERTION(aNameSpaceID
!= kNameSpaceID_Unknown
, "Must have namespace");
365 NS_ASSERTION(aValues
, "Null value array");
367 const nsAttrValue
* val
= GetAttr(aName
, aNameSpaceID
);
369 for (int32_t i
= 0; aValues
[i
]; ++i
) {
370 if (val
->Equals(aValues
[i
], aCaseSensitive
)) {
374 return ATTR_VALUE_NO_MATCH
;