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/ErrorResult.h"
22 using namespace mozilla
;
23 using namespace mozilla::dom
;
25 nsDOMTokenList::nsDOMTokenList(
26 Element
* aElement
, nsAtom
* aAttrAtom
,
27 const DOMTokenListSupportedTokenArray aSupportedTokens
)
30 mSupportedTokens(aSupportedTokens
) {
31 // We don't add a reference to our element. If it goes away,
32 // we'll be told to drop our reference
35 nsDOMTokenList::~nsDOMTokenList() = default;
37 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMTokenList
, mElement
)
39 NS_INTERFACE_MAP_BEGIN(nsDOMTokenList
)
40 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
41 NS_INTERFACE_MAP_ENTRY(nsISupports
)
42 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMTokenList
)
45 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMTokenList
)
46 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMTokenList
)
48 const nsAttrValue
* nsDOMTokenList::GetParsedAttr() {
52 return mElement
->GetAttrInfo(kNameSpaceID_None
, mAttrAtom
).mValue
;
55 static void RemoveDuplicates(const nsAttrValue
* aAttr
) {
56 if (!aAttr
|| aAttr
->Type() != nsAttrValue::eAtomArray
) {
59 const_cast<nsAttrValue
*>(aAttr
)->RemoveDuplicatesFromAtomArray();
62 uint32_t nsDOMTokenList::Length() {
63 const nsAttrValue
* attr
= GetParsedAttr();
68 RemoveDuplicates(attr
);
69 return attr
->GetAtomCount();
72 void nsDOMTokenList::IndexedGetter(uint32_t aIndex
, bool& aFound
,
74 const nsAttrValue
* attr
= GetParsedAttr();
76 if (!attr
|| aIndex
>= static_cast<uint32_t>(attr
->GetAtomCount())) {
81 RemoveDuplicates(attr
);
83 if (attr
&& aIndex
< static_cast<uint32_t>(attr
->GetAtomCount())) {
85 attr
->AtomAt(aIndex
)->ToString(aResult
);
91 void nsDOMTokenList::GetValue(nsAString
& aResult
) {
97 mElement
->GetAttr(mAttrAtom
, aResult
);
100 void nsDOMTokenList::SetValue(const nsAString
& aValue
, ErrorResult
& rv
) {
105 rv
= mElement
->SetAttr(kNameSpaceID_None
, mAttrAtom
, aValue
, true);
108 void nsDOMTokenList::CheckToken(const nsAString
& aToken
, ErrorResult
& aRv
) {
109 if (aToken
.IsEmpty()) {
110 return aRv
.ThrowSyntaxError("The empty string is not a valid token.");
113 nsAString::const_iterator iter
, end
;
114 aToken
.BeginReading(iter
);
115 aToken
.EndReading(end
);
117 while (iter
!= end
) {
118 if (nsContentUtils::IsHTMLWhitespace(*iter
)) {
119 return aRv
.ThrowInvalidCharacterError(
120 "The token can not contain whitespace.");
126 void nsDOMTokenList::CheckTokens(const nsTArray
<nsString
>& aTokens
,
128 for (uint32_t i
= 0, l
= aTokens
.Length(); i
< l
; ++i
) {
129 CheckToken(aTokens
[i
], aRv
);
136 bool nsDOMTokenList::Contains(const nsAString
& aToken
) {
137 const nsAttrValue
* attr
= GetParsedAttr();
138 return attr
&& attr
->Contains(aToken
);
141 void nsDOMTokenList::AddInternal(const nsAttrValue
* aAttr
,
142 const nsTArray
<nsString
>& aTokens
) {
147 nsAutoString resultStr
;
150 RemoveDuplicates(aAttr
);
151 for (uint32_t i
= 0; i
< aAttr
->GetAtomCount(); i
++) {
153 resultStr
.AppendLiteral(" ");
155 resultStr
.Append(nsDependentAtomString(aAttr
->AtomAt(i
)));
159 AutoTArray
<nsString
, 10> addedClasses
;
161 for (uint32_t i
= 0, l
= aTokens
.Length(); i
< l
; ++i
) {
162 const nsString
& aToken
= aTokens
[i
];
164 if ((aAttr
&& aAttr
->Contains(aToken
)) || addedClasses
.Contains(aToken
)) {
168 if (!resultStr
.IsEmpty()) {
169 resultStr
.Append(' ');
171 resultStr
.Append(aToken
);
173 addedClasses
.AppendElement(aToken
);
176 mElement
->SetAttr(kNameSpaceID_None
, mAttrAtom
, resultStr
, true);
179 void nsDOMTokenList::Add(const nsTArray
<nsString
>& aTokens
,
180 ErrorResult
& aError
) {
181 CheckTokens(aTokens
, aError
);
182 if (aError
.Failed()) {
186 const nsAttrValue
* attr
= GetParsedAttr();
187 AddInternal(attr
, aTokens
);
190 void nsDOMTokenList::Add(const nsAString
& aToken
, ErrorResult
& aError
) {
191 AutoTArray
<nsString
, 1> tokens
;
192 tokens
.AppendElement(aToken
);
196 void nsDOMTokenList::RemoveInternal(const nsAttrValue
* aAttr
,
197 const nsTArray
<nsString
>& aTokens
) {
198 MOZ_ASSERT(aAttr
, "Need an attribute");
200 RemoveDuplicates(aAttr
);
202 nsAutoString resultStr
;
203 for (uint32_t i
= 0; i
< aAttr
->GetAtomCount(); i
++) {
204 if (aTokens
.Contains(nsDependentAtomString(aAttr
->AtomAt(i
)))) {
207 if (!resultStr
.IsEmpty()) {
208 resultStr
.AppendLiteral(" ");
210 resultStr
.Append(nsDependentAtomString(aAttr
->AtomAt(i
)));
213 mElement
->SetAttr(kNameSpaceID_None
, mAttrAtom
, resultStr
, true);
216 void nsDOMTokenList::Remove(const nsTArray
<nsString
>& aTokens
,
217 ErrorResult
& aError
) {
218 CheckTokens(aTokens
, aError
);
219 if (aError
.Failed()) {
223 const nsAttrValue
* attr
= GetParsedAttr();
228 RemoveInternal(attr
, aTokens
);
231 void nsDOMTokenList::Remove(const nsAString
& aToken
, ErrorResult
& aError
) {
232 AutoTArray
<nsString
, 1> tokens
;
233 tokens
.AppendElement(aToken
);
234 Remove(tokens
, aError
);
237 bool nsDOMTokenList::Toggle(const nsAString
& aToken
,
238 const Optional
<bool>& aForce
, ErrorResult
& aError
) {
239 CheckToken(aToken
, aError
);
240 if (aError
.Failed()) {
244 const nsAttrValue
* attr
= GetParsedAttr();
245 const bool forceOn
= aForce
.WasPassed() && aForce
.Value();
246 const bool forceOff
= aForce
.WasPassed() && !aForce
.Value();
248 bool isPresent
= attr
&& attr
->Contains(aToken
);
249 AutoTArray
<nsString
, 1> tokens
;
250 (*tokens
.AppendElement()).Rebind(aToken
.Data(), aToken
.Length());
254 RemoveInternal(attr
, tokens
);
259 AddInternal(attr
, tokens
);
267 bool nsDOMTokenList::Replace(const nsAString
& aToken
,
268 const nsAString
& aNewToken
, ErrorResult
& aError
) {
269 // Doing this here instead of using `CheckToken` because if aToken had invalid
270 // characters, and aNewToken is empty, the returned error should be a
271 // SyntaxError, not an InvalidCharacterError.
272 if (aNewToken
.IsEmpty()) {
273 aError
.ThrowSyntaxError("The empty string is not a valid token.");
277 CheckToken(aToken
, aError
);
278 if (aError
.Failed()) {
282 CheckToken(aNewToken
, aError
);
283 if (aError
.Failed()) {
287 const nsAttrValue
* attr
= GetParsedAttr();
292 return ReplaceInternal(attr
, aToken
, aNewToken
);
295 bool nsDOMTokenList::ReplaceInternal(const nsAttrValue
* aAttr
,
296 const nsAString
& aToken
,
297 const nsAString
& aNewToken
) {
298 RemoveDuplicates(aAttr
);
300 // Trying to do a single pass here leads to really complicated code. Just do
302 bool haveOld
= false;
303 for (uint32_t i
= 0; i
< aAttr
->GetAtomCount(); ++i
) {
304 if (aAttr
->AtomAt(i
)->Equals(aToken
)) {
310 // Make sure to not touch the attribute value in this case.
315 nsAutoString resultStr
;
316 for (uint32_t i
= 0; i
< aAttr
->GetAtomCount(); i
++) {
317 if (aAttr
->AtomAt(i
)->Equals(aToken
) ||
318 aAttr
->AtomAt(i
)->Equals(aNewToken
)) {
320 // We keep only the first
324 if (!resultStr
.IsEmpty()) {
325 resultStr
.AppendLiteral(" ");
327 resultStr
.Append(aNewToken
);
330 if (!resultStr
.IsEmpty()) {
331 resultStr
.AppendLiteral(" ");
333 resultStr
.Append(nsDependentAtomString(aAttr
->AtomAt(i
)));
336 MOZ_ASSERT(sawIt
, "How could we not have found our token this time?");
337 mElement
->SetAttr(kNameSpaceID_None
, mAttrAtom
, resultStr
, true);
341 bool nsDOMTokenList::Supports(const nsAString
& aToken
, ErrorResult
& aError
) {
342 if (!mSupportedTokens
) {
343 aError
.ThrowTypeError
<MSG_TOKENLIST_NO_SUPPORTED_TOKENS
>(
344 NS_ConvertUTF16toUTF8(mElement
->LocalName()),
345 NS_ConvertUTF16toUTF8(nsDependentAtomString(mAttrAtom
)));
349 for (DOMTokenListSupportedToken
* supportedToken
= mSupportedTokens
;
350 *supportedToken
; ++supportedToken
) {
351 if (aToken
.LowerCaseEqualsASCII(*supportedToken
)) {
359 DocGroup
* nsDOMTokenList::GetDocGroup() const {
360 return mElement
? mElement
->OwnerDoc()->GetDocGroup() : nullptr;
363 JSObject
* nsDOMTokenList::WrapObject(JSContext
* cx
,
364 JS::Handle
<JSObject
*> aGivenProto
) {
365 return DOMTokenList_Binding::Wrap(cx
, this, aGivenProto
);