/src/mozilla-central/dom/base/nsDOMTokenList.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | * Implementation of DOMTokenList specified by HTML5. |
9 | | */ |
10 | | |
11 | | #include "nsDOMTokenList.h" |
12 | | #include "nsAttrValue.h" |
13 | | #include "nsAttrValueInlines.h" |
14 | | #include "nsDataHashtable.h" |
15 | | #include "nsError.h" |
16 | | #include "nsHashKeys.h" |
17 | | #include "mozilla/dom/Element.h" |
18 | | #include "mozilla/dom/DOMTokenListBinding.h" |
19 | | #include "mozilla/BloomFilter.h" |
20 | | #include "mozilla/ErrorResult.h" |
21 | | |
22 | | using namespace mozilla; |
23 | | using namespace mozilla::dom; |
24 | | |
25 | | nsDOMTokenList::nsDOMTokenList(Element* aElement, nsAtom* aAttrAtom, |
26 | | const DOMTokenListSupportedTokenArray aSupportedTokens) |
27 | | : mElement(aElement), |
28 | | mAttrAtom(aAttrAtom), |
29 | | mSupportedTokens(aSupportedTokens) |
30 | 0 | { |
31 | 0 | // We don't add a reference to our element. If it goes away, |
32 | 0 | // we'll be told to drop our reference |
33 | 0 | } |
34 | | |
35 | 0 | nsDOMTokenList::~nsDOMTokenList() { } |
36 | | |
37 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMTokenList, mElement) |
38 | | |
39 | 0 | NS_INTERFACE_MAP_BEGIN(nsDOMTokenList) |
40 | 0 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
41 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
42 | 0 | NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMTokenList) |
43 | 0 | NS_INTERFACE_MAP_END |
44 | | |
45 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMTokenList) |
46 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMTokenList) |
47 | | |
48 | | const nsAttrValue* |
49 | | nsDOMTokenList::GetParsedAttr() |
50 | 0 | { |
51 | 0 | if (!mElement) { |
52 | 0 | return nullptr; |
53 | 0 | } |
54 | 0 | return mElement->GetAttrInfo(kNameSpaceID_None, mAttrAtom).mValue; |
55 | 0 | } |
56 | | |
57 | | void |
58 | | nsDOMTokenList::RemoveDuplicates(const nsAttrValue* aAttr) |
59 | 0 | { |
60 | 0 | if (!aAttr || aAttr->Type() != nsAttrValue::eAtomArray) { |
61 | 0 | return; |
62 | 0 | } |
63 | 0 | |
64 | 0 | BloomFilter<8, nsAtom> filter; |
65 | 0 | AtomArray* array = aAttr->GetAtomArrayValue(); |
66 | 0 | for (uint32_t i = 0; i < array->Length(); i++) { |
67 | 0 | nsAtom* atom = array->ElementAt(i); |
68 | 0 | if (filter.mightContain(atom)) { |
69 | 0 | // Start again, with a hashtable |
70 | 0 | RemoveDuplicatesInternal(array, i); |
71 | 0 | return; |
72 | 0 | } else { |
73 | 0 | filter.add(atom); |
74 | 0 | } |
75 | 0 | } |
76 | 0 | } |
77 | | |
78 | | void |
79 | | nsDOMTokenList::RemoveDuplicatesInternal(AtomArray* aArray, uint32_t aStart) |
80 | 0 | { |
81 | 0 | nsDataHashtable<nsPtrHashKey<nsAtom>, bool> tokens; |
82 | 0 |
|
83 | 0 | for (uint32_t i = 0; i < aArray->Length(); i++) { |
84 | 0 | nsAtom* atom = aArray->ElementAt(i); |
85 | 0 | // No need to check the hashtable below aStart |
86 | 0 | if (i >= aStart && tokens.Get(atom)) { |
87 | 0 | aArray->RemoveElementAt(i); |
88 | 0 | i--; |
89 | 0 | } else { |
90 | 0 | tokens.Put(atom, true); |
91 | 0 | } |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | uint32_t |
96 | | nsDOMTokenList::Length() |
97 | 0 | { |
98 | 0 | const nsAttrValue* attr = GetParsedAttr(); |
99 | 0 | if (!attr) { |
100 | 0 | return 0; |
101 | 0 | } |
102 | 0 | |
103 | 0 | RemoveDuplicates(attr); |
104 | 0 | return attr->GetAtomCount(); |
105 | 0 | } |
106 | | |
107 | | void |
108 | | nsDOMTokenList::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aResult) |
109 | 0 | { |
110 | 0 | const nsAttrValue* attr = GetParsedAttr(); |
111 | 0 |
|
112 | 0 | if (!attr || aIndex >= static_cast<uint32_t>(attr->GetAtomCount())) { |
113 | 0 | aFound = false; |
114 | 0 | return; |
115 | 0 | } |
116 | 0 | |
117 | 0 | RemoveDuplicates(attr); |
118 | 0 |
|
119 | 0 | if (attr && aIndex < static_cast<uint32_t>(attr->GetAtomCount())) { |
120 | 0 | aFound = true; |
121 | 0 | attr->AtomAt(aIndex)->ToString(aResult); |
122 | 0 | } else { |
123 | 0 | aFound = false; |
124 | 0 | } |
125 | 0 | } |
126 | | |
127 | | void |
128 | | nsDOMTokenList::SetValue(const nsAString& aValue, ErrorResult& rv) |
129 | 0 | { |
130 | 0 | if (!mElement) { |
131 | 0 | return; |
132 | 0 | } |
133 | 0 | |
134 | 0 | rv = mElement->SetAttr(kNameSpaceID_None, mAttrAtom, aValue, true); |
135 | 0 | } |
136 | | |
137 | | nsresult |
138 | | nsDOMTokenList::CheckToken(const nsAString& aStr) |
139 | 0 | { |
140 | 0 | if (aStr.IsEmpty()) { |
141 | 0 | return NS_ERROR_DOM_SYNTAX_ERR; |
142 | 0 | } |
143 | 0 | |
144 | 0 | nsAString::const_iterator iter, end; |
145 | 0 | aStr.BeginReading(iter); |
146 | 0 | aStr.EndReading(end); |
147 | 0 |
|
148 | 0 | while (iter != end) { |
149 | 0 | if (nsContentUtils::IsHTMLWhitespace(*iter)) |
150 | 0 | return NS_ERROR_DOM_INVALID_CHARACTER_ERR; |
151 | 0 | ++iter; |
152 | 0 | } |
153 | 0 |
|
154 | 0 | return NS_OK; |
155 | 0 | } |
156 | | |
157 | | nsresult |
158 | | nsDOMTokenList::CheckTokens(const nsTArray<nsString>& aTokens) |
159 | 0 | { |
160 | 0 | for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) { |
161 | 0 | nsresult rv = CheckToken(aTokens[i]); |
162 | 0 | if (NS_FAILED(rv)) { |
163 | 0 | return rv; |
164 | 0 | } |
165 | 0 | } |
166 | 0 |
|
167 | 0 | return NS_OK; |
168 | 0 | } |
169 | | |
170 | | bool |
171 | | nsDOMTokenList::Contains(const nsAString& aToken) |
172 | 0 | { |
173 | 0 | const nsAttrValue* attr = GetParsedAttr(); |
174 | 0 | return attr && attr->Contains(aToken); |
175 | 0 | } |
176 | | |
177 | | void |
178 | | nsDOMTokenList::AddInternal(const nsAttrValue* aAttr, |
179 | | const nsTArray<nsString>& aTokens) |
180 | 0 | { |
181 | 0 | if (!mElement) { |
182 | 0 | return; |
183 | 0 | } |
184 | 0 | |
185 | 0 | nsAutoString resultStr; |
186 | 0 |
|
187 | 0 | if (aAttr) { |
188 | 0 | RemoveDuplicates(aAttr); |
189 | 0 | for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) { |
190 | 0 | if (i != 0) { |
191 | 0 | resultStr.AppendLiteral(" "); |
192 | 0 | } |
193 | 0 | resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i))); |
194 | 0 | } |
195 | 0 | } |
196 | 0 |
|
197 | 0 | AutoTArray<nsString, 10> addedClasses; |
198 | 0 |
|
199 | 0 | for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) { |
200 | 0 | const nsString& aToken = aTokens[i]; |
201 | 0 |
|
202 | 0 | if ((aAttr && aAttr->Contains(aToken)) || |
203 | 0 | addedClasses.Contains(aToken)) { |
204 | 0 | continue; |
205 | 0 | } |
206 | 0 | |
207 | 0 | if (!resultStr.IsEmpty()) { |
208 | 0 | resultStr.Append(' '); |
209 | 0 | } |
210 | 0 | resultStr.Append(aToken); |
211 | 0 |
|
212 | 0 | addedClasses.AppendElement(aToken); |
213 | 0 | } |
214 | 0 |
|
215 | 0 | mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true); |
216 | 0 | } |
217 | | |
218 | | void |
219 | | nsDOMTokenList::Add(const nsTArray<nsString>& aTokens, ErrorResult& aError) |
220 | 0 | { |
221 | 0 | aError = CheckTokens(aTokens); |
222 | 0 | if (aError.Failed()) { |
223 | 0 | return; |
224 | 0 | } |
225 | 0 | |
226 | 0 | const nsAttrValue* attr = GetParsedAttr(); |
227 | 0 | AddInternal(attr, aTokens); |
228 | 0 | } |
229 | | |
230 | | void |
231 | | nsDOMTokenList::Add(const nsAString& aToken, ErrorResult& aError) |
232 | 0 | { |
233 | 0 | AutoTArray<nsString, 1> tokens; |
234 | 0 | tokens.AppendElement(aToken); |
235 | 0 | Add(tokens, aError); |
236 | 0 | } |
237 | | |
238 | | void |
239 | | nsDOMTokenList::RemoveInternal(const nsAttrValue* aAttr, |
240 | | const nsTArray<nsString>& aTokens) |
241 | 0 | { |
242 | 0 | MOZ_ASSERT(aAttr, "Need an attribute"); |
243 | 0 |
|
244 | 0 | RemoveDuplicates(aAttr); |
245 | 0 |
|
246 | 0 | nsAutoString resultStr; |
247 | 0 | for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) { |
248 | 0 | if (aTokens.Contains(nsDependentAtomString(aAttr->AtomAt(i)))) { |
249 | 0 | continue; |
250 | 0 | } |
251 | 0 | if (!resultStr.IsEmpty()) { |
252 | 0 | resultStr.AppendLiteral(" "); |
253 | 0 | } |
254 | 0 | resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i))); |
255 | 0 | } |
256 | 0 |
|
257 | 0 | mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true); |
258 | 0 | } |
259 | | |
260 | | void |
261 | | nsDOMTokenList::Remove(const nsTArray<nsString>& aTokens, ErrorResult& aError) |
262 | 0 | { |
263 | 0 | aError = CheckTokens(aTokens); |
264 | 0 | if (aError.Failed()) { |
265 | 0 | return; |
266 | 0 | } |
267 | 0 | |
268 | 0 | const nsAttrValue* attr = GetParsedAttr(); |
269 | 0 | if (!attr) { |
270 | 0 | return; |
271 | 0 | } |
272 | 0 | |
273 | 0 | RemoveInternal(attr, aTokens); |
274 | 0 | } |
275 | | |
276 | | void |
277 | | nsDOMTokenList::Remove(const nsAString& aToken, ErrorResult& aError) |
278 | 0 | { |
279 | 0 | AutoTArray<nsString, 1> tokens; |
280 | 0 | tokens.AppendElement(aToken); |
281 | 0 | Remove(tokens, aError); |
282 | 0 | } |
283 | | |
284 | | bool |
285 | | nsDOMTokenList::Toggle(const nsAString& aToken, |
286 | | const Optional<bool>& aForce, |
287 | | ErrorResult& aError) |
288 | 0 | { |
289 | 0 | aError = CheckToken(aToken); |
290 | 0 | if (aError.Failed()) { |
291 | 0 | return false; |
292 | 0 | } |
293 | 0 | |
294 | 0 | const nsAttrValue* attr = GetParsedAttr(); |
295 | 0 | const bool forceOn = aForce.WasPassed() && aForce.Value(); |
296 | 0 | const bool forceOff = aForce.WasPassed() && !aForce.Value(); |
297 | 0 |
|
298 | 0 | bool isPresent = attr && attr->Contains(aToken); |
299 | 0 | AutoTArray<nsString, 1> tokens; |
300 | 0 | (*tokens.AppendElement()).Rebind(aToken.Data(), aToken.Length()); |
301 | 0 |
|
302 | 0 | if (isPresent) { |
303 | 0 | if (!forceOn) { |
304 | 0 | RemoveInternal(attr, tokens); |
305 | 0 | isPresent = false; |
306 | 0 | } |
307 | 0 | } else { |
308 | 0 | if (!forceOff) { |
309 | 0 | AddInternal(attr, tokens); |
310 | 0 | isPresent = true; |
311 | 0 | } |
312 | 0 | } |
313 | 0 |
|
314 | 0 | return isPresent; |
315 | 0 | } |
316 | | |
317 | | bool |
318 | | nsDOMTokenList::Replace(const nsAString& aToken, |
319 | | const nsAString& aNewToken, |
320 | | ErrorResult& aError) |
321 | 0 | { |
322 | 0 | // Doing this here instead of using `CheckToken` because if aToken had invalid |
323 | 0 | // characters, and aNewToken is empty, the returned error should be a |
324 | 0 | // SyntaxError, not an InvalidCharacterError. |
325 | 0 | if (aNewToken.IsEmpty()) { |
326 | 0 | aError.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
327 | 0 | return false; |
328 | 0 | } |
329 | 0 | |
330 | 0 | aError = CheckToken(aToken); |
331 | 0 | if (aError.Failed()) { |
332 | 0 | return false; |
333 | 0 | } |
334 | 0 | |
335 | 0 | aError = CheckToken(aNewToken); |
336 | 0 | if (aError.Failed()) { |
337 | 0 | return false; |
338 | 0 | } |
339 | 0 | |
340 | 0 | const nsAttrValue* attr = GetParsedAttr(); |
341 | 0 | if (!attr) { |
342 | 0 | return false; |
343 | 0 | } |
344 | 0 | |
345 | 0 | return ReplaceInternal(attr, aToken, aNewToken); |
346 | 0 | } |
347 | | |
348 | | bool |
349 | | nsDOMTokenList::ReplaceInternal(const nsAttrValue* aAttr, |
350 | | const nsAString& aToken, |
351 | | const nsAString& aNewToken) |
352 | 0 | { |
353 | 0 | RemoveDuplicates(aAttr); |
354 | 0 |
|
355 | 0 | // Trying to do a single pass here leads to really complicated code. Just do |
356 | 0 | // the simple thing. |
357 | 0 | bool haveOld = false; |
358 | 0 | for (uint32_t i = 0; i < aAttr->GetAtomCount(); ++i) { |
359 | 0 | if (aAttr->AtomAt(i)->Equals(aToken)) { |
360 | 0 | haveOld = true; |
361 | 0 | break; |
362 | 0 | } |
363 | 0 | } |
364 | 0 | if (!haveOld) { |
365 | 0 | // Make sure to not touch the attribute value in this case. |
366 | 0 | return false; |
367 | 0 | } |
368 | 0 | |
369 | 0 | bool sawIt = false; |
370 | 0 | nsAutoString resultStr; |
371 | 0 | for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) { |
372 | 0 | if (aAttr->AtomAt(i)->Equals(aToken) || |
373 | 0 | aAttr->AtomAt(i)->Equals(aNewToken)) { |
374 | 0 | if (sawIt) { |
375 | 0 | // We keep only the first |
376 | 0 | continue; |
377 | 0 | } |
378 | 0 | sawIt = true; |
379 | 0 | if (!resultStr.IsEmpty()) { |
380 | 0 | resultStr.AppendLiteral(" "); |
381 | 0 | } |
382 | 0 | resultStr.Append(aNewToken); |
383 | 0 | continue; |
384 | 0 | } |
385 | 0 | if (!resultStr.IsEmpty()) { |
386 | 0 | resultStr.AppendLiteral(" "); |
387 | 0 | } |
388 | 0 | resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i))); |
389 | 0 | } |
390 | 0 |
|
391 | 0 | MOZ_ASSERT(sawIt, "How could we not have found our token this time?"); |
392 | 0 | mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true); |
393 | 0 | return true; |
394 | 0 | } |
395 | | |
396 | | bool |
397 | | nsDOMTokenList::Supports(const nsAString& aToken, |
398 | | ErrorResult& aError) |
399 | 0 | { |
400 | 0 | if (!mSupportedTokens) { |
401 | 0 | aError.ThrowTypeError<MSG_TOKENLIST_NO_SUPPORTED_TOKENS>( |
402 | 0 | mElement->LocalName(), |
403 | 0 | nsDependentAtomString(mAttrAtom)); |
404 | 0 | return false; |
405 | 0 | } |
406 | 0 | |
407 | 0 | for (DOMTokenListSupportedToken* supportedToken = mSupportedTokens; |
408 | 0 | *supportedToken; |
409 | 0 | ++supportedToken) { |
410 | 0 | if (aToken.LowerCaseEqualsASCII(*supportedToken)) { |
411 | 0 | return true; |
412 | 0 | } |
413 | 0 | } |
414 | 0 |
|
415 | 0 | return false; |
416 | 0 | } |
417 | | |
418 | | void |
419 | | nsDOMTokenList::Stringify(nsAString& aResult) |
420 | 0 | { |
421 | 0 | if (!mElement) { |
422 | 0 | aResult.Truncate(); |
423 | 0 | return; |
424 | 0 | } |
425 | 0 | |
426 | 0 | mElement->GetAttr(kNameSpaceID_None, mAttrAtom, aResult); |
427 | 0 | } |
428 | | |
429 | | DocGroup* |
430 | | nsDOMTokenList::GetDocGroup() const |
431 | 0 | { |
432 | 0 | return mElement ? mElement->OwnerDoc()->GetDocGroup() : nullptr; |
433 | 0 | } |
434 | | |
435 | | JSObject* |
436 | | nsDOMTokenList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) |
437 | 0 | { |
438 | 0 | return DOMTokenList_Binding::Wrap(cx, this, aGivenProto); |
439 | 0 | } |
440 | | |