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