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 * Implementation of DOMTokenList specified by HTML5.
11 #include "nsDOMTokenList.h"
12 #include "nsAttrValue.h"
13 #include "nsAttrValueInlines.h"
14 #include "nsTHashMap.h"
16 #include "nsHashKeys.h"
17 #include "mozilla/dom/Document.h"
18 #include "mozilla/dom/Element.h"
19 #include "mozilla/dom/DOMTokenListBinding.h"
20 #include "mozilla/BloomFilter.h"
21 #include "mozilla/ErrorResult.h"
23 using namespace mozilla
;
24 using namespace mozilla::dom
;
26 nsDOMTokenList::nsDOMTokenList(
27 Element
* aElement
, nsAtom
* aAttrAtom
,
28 const DOMTokenListSupportedTokenArray aSupportedTokens
)
31 mSupportedTokens(aSupportedTokens
) {
32 // We don't add a reference to our element. If it goes away,
33 // we'll be told to drop our reference
36 nsDOMTokenList::~nsDOMTokenList() = default;
38 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMTokenList
, mElement
)
40 NS_INTERFACE_MAP_BEGIN(nsDOMTokenList
)
41 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
42 NS_INTERFACE_MAP_ENTRY(nsISupports
)
43 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMTokenList
)
46 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMTokenList
)
47 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMTokenList
)
49 const nsAttrValue
* nsDOMTokenList::GetParsedAttr() {
53 return mElement
->GetAttrInfo(kNameSpaceID_None
, mAttrAtom
).mValue
;
56 static void RemoveDuplicatesInternal(AtomArray
* aArray
, uint32_t aStart
) {
57 nsTHashMap
<nsPtrHashKey
<nsAtom
>, bool> tokens
;
59 for (uint32_t i
= 0; i
< aArray
->Length(); i
++) {
60 nsAtom
* atom
= aArray
->ElementAt(i
);
61 // No need to check the hashtable below aStart
62 if (i
>= aStart
&& tokens
.Get(atom
)) {
63 aArray
->RemoveElementAt(i
);
66 tokens
.InsertOrUpdate(atom
, true);
71 void nsDOMTokenList::RemoveDuplicates(const nsAttrValue
* aAttr
) {
72 if (!aAttr
|| aAttr
->Type() != nsAttrValue::eAtomArray
) {
76 BitBloomFilter
<8, nsAtom
> filter
;
77 AtomArray
* array
= aAttr
->GetAtomArrayValue();
78 for (uint32_t i
= 0; i
< array
->Length(); i
++) {
79 nsAtom
* atom
= array
->ElementAt(i
);
80 if (filter
.mightContain(atom
)) {
81 // Start again, with a hashtable
82 RemoveDuplicatesInternal(array
, i
);
90 uint32_t nsDOMTokenList::Length() {
91 const nsAttrValue
* attr
= GetParsedAttr();
96 RemoveDuplicates(attr
);
97 return attr
->GetAtomCount();
100 void nsDOMTokenList::IndexedGetter(uint32_t aIndex
, bool& aFound
,
101 nsAString
& aResult
) {
102 const nsAttrValue
* attr
= GetParsedAttr();
104 if (!attr
|| aIndex
>= static_cast<uint32_t>(attr
->GetAtomCount())) {
109 RemoveDuplicates(attr
);
111 if (attr
&& aIndex
< static_cast<uint32_t>(attr
->GetAtomCount())) {
113 attr
->AtomAt(aIndex
)->ToString(aResult
);
119 void nsDOMTokenList::GetValue(nsAString
& aResult
) {
125 mElement
->GetAttr(kNameSpaceID_None
, mAttrAtom
, aResult
);
128 void nsDOMTokenList::SetValue(const nsAString
& aValue
, ErrorResult
& rv
) {
133 rv
= mElement
->SetAttr(kNameSpaceID_None
, mAttrAtom
, aValue
, true);
136 nsresult
nsDOMTokenList::CheckToken(const nsAString
& aStr
) {
137 if (aStr
.IsEmpty()) {
138 return NS_ERROR_DOM_SYNTAX_ERR
;
141 nsAString::const_iterator iter
, end
;
142 aStr
.BeginReading(iter
);
143 aStr
.EndReading(end
);
145 while (iter
!= end
) {
146 if (nsContentUtils::IsHTMLWhitespace(*iter
))
147 return NS_ERROR_DOM_INVALID_CHARACTER_ERR
;
154 nsresult
nsDOMTokenList::CheckTokens(const nsTArray
<nsString
>& aTokens
) {
155 for (uint32_t i
= 0, l
= aTokens
.Length(); i
< l
; ++i
) {
156 nsresult rv
= CheckToken(aTokens
[i
]);
165 bool nsDOMTokenList::Contains(const nsAString
& aToken
) {
166 const nsAttrValue
* attr
= GetParsedAttr();
167 return attr
&& attr
->Contains(aToken
);
170 void nsDOMTokenList::AddInternal(const nsAttrValue
* aAttr
,
171 const nsTArray
<nsString
>& aTokens
) {
176 nsAutoString resultStr
;
179 RemoveDuplicates(aAttr
);
180 for (uint32_t i
= 0; i
< aAttr
->GetAtomCount(); i
++) {
182 resultStr
.AppendLiteral(" ");
184 resultStr
.Append(nsDependentAtomString(aAttr
->AtomAt(i
)));
188 AutoTArray
<nsString
, 10> addedClasses
;
190 for (uint32_t i
= 0, l
= aTokens
.Length(); i
< l
; ++i
) {
191 const nsString
& aToken
= aTokens
[i
];
193 if ((aAttr
&& aAttr
->Contains(aToken
)) || addedClasses
.Contains(aToken
)) {
197 if (!resultStr
.IsEmpty()) {
198 resultStr
.Append(' ');
200 resultStr
.Append(aToken
);
202 addedClasses
.AppendElement(aToken
);
205 mElement
->SetAttr(kNameSpaceID_None
, mAttrAtom
, resultStr
, true);
208 void nsDOMTokenList::Add(const nsTArray
<nsString
>& aTokens
,
209 ErrorResult
& aError
) {
210 aError
= CheckTokens(aTokens
);
211 if (aError
.Failed()) {
215 const nsAttrValue
* attr
= GetParsedAttr();
216 AddInternal(attr
, aTokens
);
219 void nsDOMTokenList::Add(const nsAString
& aToken
, ErrorResult
& aError
) {
220 AutoTArray
<nsString
, 1> tokens
;
221 tokens
.AppendElement(aToken
);
225 void nsDOMTokenList::RemoveInternal(const nsAttrValue
* aAttr
,
226 const nsTArray
<nsString
>& aTokens
) {
227 MOZ_ASSERT(aAttr
, "Need an attribute");
229 RemoveDuplicates(aAttr
);
231 nsAutoString resultStr
;
232 for (uint32_t i
= 0; i
< aAttr
->GetAtomCount(); i
++) {
233 if (aTokens
.Contains(nsDependentAtomString(aAttr
->AtomAt(i
)))) {
236 if (!resultStr
.IsEmpty()) {
237 resultStr
.AppendLiteral(" ");
239 resultStr
.Append(nsDependentAtomString(aAttr
->AtomAt(i
)));
242 mElement
->SetAttr(kNameSpaceID_None
, mAttrAtom
, resultStr
, true);
245 void nsDOMTokenList::Remove(const nsTArray
<nsString
>& aTokens
,
246 ErrorResult
& aError
) {
247 aError
= CheckTokens(aTokens
);
248 if (aError
.Failed()) {
252 const nsAttrValue
* attr
= GetParsedAttr();
257 RemoveInternal(attr
, aTokens
);
260 void nsDOMTokenList::Remove(const nsAString
& aToken
, ErrorResult
& aError
) {
261 AutoTArray
<nsString
, 1> tokens
;
262 tokens
.AppendElement(aToken
);
263 Remove(tokens
, aError
);
266 bool nsDOMTokenList::Toggle(const nsAString
& aToken
,
267 const Optional
<bool>& aForce
, ErrorResult
& aError
) {
268 aError
= CheckToken(aToken
);
269 if (aError
.Failed()) {
273 const nsAttrValue
* attr
= GetParsedAttr();
274 const bool forceOn
= aForce
.WasPassed() && aForce
.Value();
275 const bool forceOff
= aForce
.WasPassed() && !aForce
.Value();
277 bool isPresent
= attr
&& attr
->Contains(aToken
);
278 AutoTArray
<nsString
, 1> tokens
;
279 (*tokens
.AppendElement()).Rebind(aToken
.Data(), aToken
.Length());
283 RemoveInternal(attr
, tokens
);
288 AddInternal(attr
, tokens
);
296 bool nsDOMTokenList::Replace(const nsAString
& aToken
,
297 const nsAString
& aNewToken
, ErrorResult
& aError
) {
298 // Doing this here instead of using `CheckToken` because if aToken had invalid
299 // characters, and aNewToken is empty, the returned error should be a
300 // SyntaxError, not an InvalidCharacterError.
301 if (aNewToken
.IsEmpty()) {
302 aError
.Throw(NS_ERROR_DOM_SYNTAX_ERR
);
306 aError
= CheckToken(aToken
);
307 if (aError
.Failed()) {
311 aError
= CheckToken(aNewToken
);
312 if (aError
.Failed()) {
316 const nsAttrValue
* attr
= GetParsedAttr();
321 return ReplaceInternal(attr
, aToken
, aNewToken
);
324 bool nsDOMTokenList::ReplaceInternal(const nsAttrValue
* aAttr
,
325 const nsAString
& aToken
,
326 const nsAString
& aNewToken
) {
327 RemoveDuplicates(aAttr
);
329 // Trying to do a single pass here leads to really complicated code. Just do
331 bool haveOld
= false;
332 for (uint32_t i
= 0; i
< aAttr
->GetAtomCount(); ++i
) {
333 if (aAttr
->AtomAt(i
)->Equals(aToken
)) {
339 // Make sure to not touch the attribute value in this case.
344 nsAutoString resultStr
;
345 for (uint32_t i
= 0; i
< aAttr
->GetAtomCount(); i
++) {
346 if (aAttr
->AtomAt(i
)->Equals(aToken
) ||
347 aAttr
->AtomAt(i
)->Equals(aNewToken
)) {
349 // We keep only the first
353 if (!resultStr
.IsEmpty()) {
354 resultStr
.AppendLiteral(" ");
356 resultStr
.Append(aNewToken
);
359 if (!resultStr
.IsEmpty()) {
360 resultStr
.AppendLiteral(" ");
362 resultStr
.Append(nsDependentAtomString(aAttr
->AtomAt(i
)));
365 MOZ_ASSERT(sawIt
, "How could we not have found our token this time?");
366 mElement
->SetAttr(kNameSpaceID_None
, mAttrAtom
, resultStr
, true);
370 bool nsDOMTokenList::Supports(const nsAString
& aToken
, ErrorResult
& aError
) {
371 if (!mSupportedTokens
) {
372 aError
.ThrowTypeError
<MSG_TOKENLIST_NO_SUPPORTED_TOKENS
>(
373 NS_ConvertUTF16toUTF8(mElement
->LocalName()),
374 NS_ConvertUTF16toUTF8(nsDependentAtomString(mAttrAtom
)));
378 for (DOMTokenListSupportedToken
* supportedToken
= mSupportedTokens
;
379 *supportedToken
; ++supportedToken
) {
380 if (aToken
.LowerCaseEqualsASCII(*supportedToken
)) {
388 DocGroup
* nsDOMTokenList::GetDocGroup() const {
389 return mElement
? mElement
->OwnerDoc()->GetDocGroup() : nullptr;
392 JSObject
* nsDOMTokenList::WrapObject(JSContext
* cx
,
393 JS::Handle
<JSObject
*> aGivenProto
) {
394 return DOMTokenList_Binding::Wrap(cx
, this, aGivenProto
);