Backed out 4 changesets (bug 1825722) for causing reftest failures CLOSED TREE
[gecko.git] / dom / base / nsDOMTokenList.cpp
blob5140b484a11ebba713cfb4a0307f4820f94b531b
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/. */
7 /*
8 * Implementation of DOMTokenList specified by HTML5.
9 */
11 #include "nsDOMTokenList.h"
12 #include "nsAttrValue.h"
13 #include "nsAttrValueInlines.h"
14 #include "nsTHashMap.h"
15 #include "nsError.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)
28 : mElement(aElement),
29 mAttrAtom(aAttrAtom),
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)
43 NS_INTERFACE_MAP_END
45 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMTokenList)
46 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMTokenList)
48 const nsAttrValue* nsDOMTokenList::GetParsedAttr() {
49 if (!mElement) {
50 return nullptr;
52 return mElement->GetAttrInfo(kNameSpaceID_None, mAttrAtom).mValue;
55 static void RemoveDuplicates(const nsAttrValue* aAttr) {
56 if (!aAttr || aAttr->Type() != nsAttrValue::eAtomArray) {
57 return;
59 const_cast<nsAttrValue*>(aAttr)->RemoveDuplicatesFromAtomArray();
62 uint32_t nsDOMTokenList::Length() {
63 const nsAttrValue* attr = GetParsedAttr();
64 if (!attr) {
65 return 0;
68 RemoveDuplicates(attr);
69 return attr->GetAtomCount();
72 void nsDOMTokenList::IndexedGetter(uint32_t aIndex, bool& aFound,
73 nsAString& aResult) {
74 const nsAttrValue* attr = GetParsedAttr();
76 if (!attr || aIndex >= static_cast<uint32_t>(attr->GetAtomCount())) {
77 aFound = false;
78 return;
81 RemoveDuplicates(attr);
83 if (attr && aIndex < static_cast<uint32_t>(attr->GetAtomCount())) {
84 aFound = true;
85 attr->AtomAt(aIndex)->ToString(aResult);
86 } else {
87 aFound = false;
91 void nsDOMTokenList::GetValue(nsAString& aResult) {
92 if (!mElement) {
93 aResult.Truncate();
94 return;
97 mElement->GetAttr(mAttrAtom, aResult);
100 void nsDOMTokenList::SetValue(const nsAString& aValue, ErrorResult& rv) {
101 if (!mElement) {
102 return;
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.");
122 ++iter;
126 void nsDOMTokenList::CheckTokens(const nsTArray<nsString>& aTokens,
127 ErrorResult& aRv) {
128 for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
129 CheckToken(aTokens[i], aRv);
130 if (aRv.Failed()) {
131 return;
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) {
143 if (!mElement) {
144 return;
147 nsAutoString resultStr;
149 if (aAttr) {
150 RemoveDuplicates(aAttr);
151 for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
152 if (i != 0) {
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)) {
165 continue;
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()) {
183 return;
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);
193 Add(tokens, aError);
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)))) {
205 continue;
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()) {
220 return;
223 const nsAttrValue* attr = GetParsedAttr();
224 if (!attr) {
225 return;
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()) {
241 return false;
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());
252 if (isPresent) {
253 if (!forceOn) {
254 RemoveInternal(attr, tokens);
255 isPresent = false;
257 } else {
258 if (!forceOff) {
259 AddInternal(attr, tokens);
260 isPresent = true;
264 return isPresent;
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.");
274 return false;
277 CheckToken(aToken, aError);
278 if (aError.Failed()) {
279 return false;
282 CheckToken(aNewToken, aError);
283 if (aError.Failed()) {
284 return false;
287 const nsAttrValue* attr = GetParsedAttr();
288 if (!attr) {
289 return false;
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
301 // the simple thing.
302 bool haveOld = false;
303 for (uint32_t i = 0; i < aAttr->GetAtomCount(); ++i) {
304 if (aAttr->AtomAt(i)->Equals(aToken)) {
305 haveOld = true;
306 break;
309 if (!haveOld) {
310 // Make sure to not touch the attribute value in this case.
311 return false;
314 bool sawIt = false;
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)) {
319 if (sawIt) {
320 // We keep only the first
321 continue;
323 sawIt = true;
324 if (!resultStr.IsEmpty()) {
325 resultStr.AppendLiteral(" ");
327 resultStr.Append(aNewToken);
328 continue;
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);
338 return 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)));
346 return false;
349 for (DOMTokenListSupportedToken* supportedToken = mSupportedTokens;
350 *supportedToken; ++supportedToken) {
351 if (aToken.LowerCaseEqualsASCII(*supportedToken)) {
352 return true;
356 return false;
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);