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/. */
6 
7 /*
8  * A unique per-element set of attributes that is used as an
9  * nsIStyleRule; used to implement presentational attributes.
10  */
11 
12 #include "nsMappedAttributes.h"
13 #include "mozilla/Assertions.h"
14 #include "nsHTMLStyleSheet.h"
15 #include "mozilla/DeclarationBlock.h"
16 #include "mozilla/HashFunctions.h"
17 #include "mozilla/MappedDeclarations.h"
18 #include "mozilla/MemoryReporting.h"
19 
20 using namespace mozilla;
21 
22 bool nsMappedAttributes::sShuttingDown = false;
23 nsTArray<void*>* nsMappedAttributes::sCachedMappedAttributeAllocations =
24     nullptr;
25 
Shutdown()26 void nsMappedAttributes::Shutdown() {
27   sShuttingDown = true;
28   if (sCachedMappedAttributeAllocations) {
29     for (uint32_t i = 0; i < sCachedMappedAttributeAllocations->Length(); ++i) {
30       void* cachedValue = (*sCachedMappedAttributeAllocations)[i];
31       ::operator delete(cachedValue);
32     }
33   }
34 
35   delete sCachedMappedAttributeAllocations;
36   sCachedMappedAttributeAllocations = nullptr;
37 }
38 
nsMappedAttributes(nsHTMLStyleSheet * aSheet,nsMapRuleToAttributesFunc aMapRuleFunc)39 nsMappedAttributes::nsMappedAttributes(nsHTMLStyleSheet* aSheet,
40                                        nsMapRuleToAttributesFunc aMapRuleFunc)
41     : mAttrCount(0),
42       mSheet(aSheet),
43       mRuleMapper(aMapRuleFunc),
44       mServoStyle(nullptr) {
45   MOZ_ASSERT(mRefCnt == 0);  // Ensure caching works as expected.
46 }
47 
nsMappedAttributes(const nsMappedAttributes & aCopy)48 nsMappedAttributes::nsMappedAttributes(const nsMappedAttributes& aCopy)
49     : mAttrCount(aCopy.mAttrCount),
50       mSheet(aCopy.mSheet),
51       mRuleMapper(aCopy.mRuleMapper),
52       // This is only called by ::Clone, which is used to create independent
53       // nsMappedAttributes objects which should not share a DeclarationBlock
54       mServoStyle(nullptr) {
55   MOZ_ASSERT(mBufferSize >= aCopy.mAttrCount, "can't fit attributes");
56   MOZ_ASSERT(mRefCnt == 0);  // Ensure caching works as expected.
57 
58   uint32_t i = 0;
59   for (const InternalAttr& attr : aCopy.Attrs()) {
60     new (&mBuffer[i++]) InternalAttr(attr);
61   }
62 }
63 
~nsMappedAttributes()64 nsMappedAttributes::~nsMappedAttributes() {
65   if (mSheet) {
66     mSheet->DropMappedAttributes(this);
67   }
68 
69   for (InternalAttr& attr : Attrs()) {
70     attr.~InternalAttr();
71   }
72 }
73 
Clone(bool aWillAddAttr)74 nsMappedAttributes* nsMappedAttributes::Clone(bool aWillAddAttr) {
75   uint32_t extra = aWillAddAttr ? 1 : 0;
76 
77   // This will call the overridden operator new
78   return new (mAttrCount + extra) nsMappedAttributes(*this);
79 }
80 
operator new(size_t aSize,uint32_t aAttrCount)81 void* nsMappedAttributes::operator new(size_t aSize,
82                                        uint32_t aAttrCount) noexcept(true) {
83   size_t size = aSize + aAttrCount * sizeof(InternalAttr);
84 
85   if (sCachedMappedAttributeAllocations) {
86     void* cached = sCachedMappedAttributeAllocations->SafeElementAt(aAttrCount);
87     if (cached) {
88       (*sCachedMappedAttributeAllocations)[aAttrCount] = nullptr;
89       return cached;
90     }
91   }
92 
93   void* newAttrs = ::operator new(size);
94 
95 #ifdef DEBUG
96   static_cast<nsMappedAttributes*>(newAttrs)->mBufferSize = aAttrCount;
97 #endif
98   return newAttrs;
99 }
100 
LastRelease()101 void nsMappedAttributes::LastRelease() {
102   if (!sShuttingDown) {
103     if (!sCachedMappedAttributeAllocations) {
104       sCachedMappedAttributeAllocations = new nsTArray<void*>();
105     }
106 
107     // Ensure the cache array is at least mAttrCount + 1 long and
108     // that each item is either null or pointing to a cached item.
109     // The size of the array is capped because mapped attributes are defined
110     // statically in element implementations.
111     sCachedMappedAttributeAllocations->SetCapacity(mAttrCount + 1);
112     for (uint32_t i = sCachedMappedAttributeAllocations->Length();
113          i < (uint32_t(mAttrCount) + 1); ++i) {
114       sCachedMappedAttributeAllocations->AppendElement(nullptr);
115     }
116 
117     if (!(*sCachedMappedAttributeAllocations)[mAttrCount]) {
118       void* memoryToCache = this;
119       this->~nsMappedAttributes();
120       (*sCachedMappedAttributeAllocations)[mAttrCount] = memoryToCache;
121       return;
122     }
123   }
124 
125   delete this;
126 }
127 
SetAndSwapAttr(nsAtom * aAttrName,nsAttrValue & aValue,bool * aValueWasSet)128 void nsMappedAttributes::SetAndSwapAttr(nsAtom* aAttrName, nsAttrValue& aValue,
129                                         bool* aValueWasSet) {
130   MOZ_ASSERT(aAttrName, "null name");
131   *aValueWasSet = false;
132   uint32_t i;
133   for (i = 0; i < mAttrCount && !mBuffer[i].mName.IsSmaller(aAttrName); ++i) {
134     if (mBuffer[i].mName.Equals(aAttrName)) {
135       mBuffer[i].mValue.SwapValueWith(aValue);
136       *aValueWasSet = true;
137       return;
138     }
139   }
140 
141   MOZ_ASSERT(mBufferSize >= mAttrCount + 1, "can't fit attributes");
142 
143   if (mAttrCount != i) {
144     memmove(&mBuffer[i + 1], &mBuffer[i],
145             (mAttrCount - i) * sizeof(InternalAttr));
146   }
147 
148   new (&mBuffer[i].mName) nsAttrName(aAttrName);
149   new (&mBuffer[i].mValue) nsAttrValue();
150   mBuffer[i].mValue.SwapValueWith(aValue);
151   ++mAttrCount;
152 }
153 
GetAttr(const nsAtom * aAttrName) const154 const nsAttrValue* nsMappedAttributes::GetAttr(const nsAtom* aAttrName) const {
155   MOZ_ASSERT(aAttrName, "null name");
156   for (const InternalAttr& attr : Attrs()) {
157     if (attr.mName.Equals(aAttrName)) {
158       return &attr.mValue;
159     }
160   }
161   return nullptr;
162 }
163 
GetAttr(const nsAString & aAttrName) const164 const nsAttrValue* nsMappedAttributes::GetAttr(
165     const nsAString& aAttrName) const {
166   for (const InternalAttr& attr : Attrs()) {
167     if (attr.mName.Atom()->Equals(aAttrName)) {
168       return &attr.mValue;
169     }
170   }
171   return nullptr;
172 }
173 
Equals(const nsMappedAttributes * aOther) const174 bool nsMappedAttributes::Equals(const nsMappedAttributes* aOther) const {
175   if (this == aOther) {
176     return true;
177   }
178 
179   if (mRuleMapper != aOther->mRuleMapper || mAttrCount != aOther->mAttrCount) {
180     return false;
181   }
182 
183   uint32_t i;
184   for (i = 0; i < mAttrCount; ++i) {
185     if (!mBuffer[i].mName.Equals(aOther->mBuffer[i].mName) ||
186         !mBuffer[i].mValue.Equals(aOther->mBuffer[i].mValue)) {
187       return false;
188     }
189   }
190 
191   return true;
192 }
193 
HashValue() const194 PLDHashNumber nsMappedAttributes::HashValue() const {
195   PLDHashNumber hash = HashGeneric(mRuleMapper);
196   for (const InternalAttr& attr : Attrs()) {
197     hash = AddToHash(hash, attr.mName.HashValue(), attr.mValue.HashValue());
198   }
199   return hash;
200 }
201 
SetStyleSheet(nsHTMLStyleSheet * aSheet)202 void nsMappedAttributes::SetStyleSheet(nsHTMLStyleSheet* aSheet) {
203   MOZ_ASSERT(!mSheet,
204              "Should either drop the sheet reference manually, "
205              "or drop the mapped attributes");
206   mSheet = aSheet;  // not ref counted
207 }
208 
RemoveAttrAt(uint32_t aPos,nsAttrValue & aValue)209 void nsMappedAttributes::RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue) {
210   mBuffer[aPos].mValue.SwapValueWith(aValue);
211   mBuffer[aPos].~InternalAttr();
212   memmove(&mBuffer[aPos], &mBuffer[aPos + 1],
213           (mAttrCount - aPos - 1) * sizeof(InternalAttr));
214   mAttrCount--;
215 }
216 
GetExistingAttrNameFromQName(const nsAString & aName) const217 const nsAttrName* nsMappedAttributes::GetExistingAttrNameFromQName(
218     const nsAString& aName) const {
219   for (const InternalAttr& attr : Attrs()) {
220     if (attr.mName.IsAtom()) {
221       if (attr.mName.Atom()->Equals(aName)) {
222         return &attr.mName;
223       }
224     } else {
225       if (attr.mName.NodeInfo()->QualifiedNameEquals(aName)) {
226         return &attr.mName;
227       }
228     }
229   }
230 
231   return nullptr;
232 }
233 
IndexOfAttr(const nsAtom * aLocalName) const234 int32_t nsMappedAttributes::IndexOfAttr(const nsAtom* aLocalName) const {
235   for (uint32_t i = 0; i < mAttrCount; ++i) {
236     if (mBuffer[i].mName.Equals(aLocalName)) {
237       return i;
238     }
239   }
240   return -1;
241 }
242 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const243 size_t nsMappedAttributes::SizeOfIncludingThis(
244     MallocSizeOf aMallocSizeOf) const {
245   MOZ_ASSERT(mBufferSize >= mAttrCount, "can't fit attributes");
246 
247   size_t n = aMallocSizeOf(this);
248   for (const InternalAttr& attr : Attrs()) {
249     n += attr.mValue.SizeOfExcludingThis(aMallocSizeOf);
250   }
251   return n;
252 }
253 
LazilyResolveServoDeclaration(dom::Document * aDoc)254 void nsMappedAttributes::LazilyResolveServoDeclaration(dom::Document* aDoc) {
255   MOZ_ASSERT(!mServoStyle,
256              "LazilyResolveServoDeclaration should not be called if "
257              "mServoStyle is already set");
258   if (mRuleMapper) {
259     MappedDeclarations declarations(
260         aDoc, Servo_DeclarationBlock_CreateEmpty().Consume());
261     (*mRuleMapper)(this, declarations);
262     mServoStyle = declarations.TakeDeclarationBlock();
263   }
264 }
265