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 // 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
));
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()) {
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
)) {
205 int32_t AttrArray::IndexOfAttr(const nsAtom
* aLocalName
) const {
207 for (const InternalAttr
& attr
: Attrs()) {
208 if (attr
.mName
.Equals(aLocalName
)) {
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
);
223 for (const InternalAttr
& attr
: Attrs()) {
224 if (attr
.mName
.Equals(aLocalName
, aNamespaceID
)) {
232 void AttrArray::Compact() {
237 if (!mImpl
->mAttrCount
&& !mImpl
->mMappedAttributeBits
) {
243 if (mImpl
->mAttrCount
== mImpl
->mCapacity
) {
247 Impl
* oldImpl
= mImpl
.release();
248 Impl
* impl
= static_cast<Impl
*>(
249 realloc(oldImpl
, Impl::AllocationSizeForAttributes(oldImpl
->mAttrCount
)));
251 mImpl
.reset(oldImpl
);
254 impl
->mCapacity
= impl
->mAttrCount
;
258 nsresult
AttrArray::EnsureCapacityToClone(const AttrArray
& aOther
) {
260 "AttrArray::EnsureCapacityToClone requires the array be empty "
263 uint32_t attrCount
= aOther
.AttrCount();
268 // No need to use a CheckedUint32 because we are cloning. We know that we
269 // have already allocated an AttrArray of this size.
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;
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()) {
292 if (capacity
.value() <= kLinearThreshold
) {
294 capacity
+= kLinearGrowSize
;
295 if (!capacity
.isValid()) {
298 } while (capacity
.value() < minCapacity
.value());
300 uint32_t shift
= mozilla::CeilingLog2(minCapacity
.value());
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
) {
316 CheckedUint32 sizeInBytes
= aCapacity
;
317 sizeInBytes
*= sizeof(InternalAttr
);
318 if (!sizeInBytes
.isValid()) {
322 sizeInBytes
+= sizeof(Impl
);
323 if (!sizeInBytes
.isValid()) {
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()));
334 mImpl
.reset(oldImpl
);
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
;
350 size_t AttrArray::SizeOfExcludingThis(
351 mozilla::MallocSizeOf aMallocSizeOf
) const {
355 size_t n
= aMallocSizeOf(mImpl
.get());
356 for (const InternalAttr
& attr
: Attrs()) {
357 n
+= attr
.mValue
.SizeOfExcludingThis(aMallocSizeOf
);
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
);
371 for (int32_t i
= 0; aValues
[i
]; ++i
) {
372 if (val
->Equals(aValues
[i
], aCaseSensitive
)) {
376 return ATTR_VALUE_NO_MATCH
;